home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 26
/
Cream of the Crop 26.iso
/
os2
/
bullet25.zip
/
common
/
bullet2.inf
(
.txt
)
< prev
next >
Wrap
OS/2 Help File
|
1997-06-25
|
277KB
|
11,413 lines
ΓòÉΓòÉΓòÉ 1. Copyright ΓòÉΓòÉΓòÉ
25-Jun-1997
BULLET is Copyright 1992-97, and is owned by the author, Cornel Huth,
and is protected by United States copyright laws and international
treaty provisions. License restrictions apply.
ΓòÉΓòÉΓòÉ 2. License Agreement ΓòÉΓòÉΓòÉ
Before using this software, BULLET you must agree to the following:
1. A BULLET license grants you the right to use the BULLET library code on a
royalty-free basis according to the terms of this License Agreement.
2. You are required to have one license per developer that is programming
with Bullet.
3. There is no restriction on the number of users you may support, and no
restriction on the number of different end-user programs you may
distribute that use BULLET. You may allow any number of simultaneous
users to use your end-user program.
4. The dynamic link library, BULLET*.DLL, may be distributed with your
end-user program. No other BULLET product may be distributed without
permission.
5. Evaluation use is limited to 300 records, and for the sole purpose of
evaluating the software. The BULLET library code may not be distributed
in any form without a registered BULLET license. A BULLET license is
obtained only with purchase of a BULLET package, purchased from an
authorized BULLET distributor.
6. BULLET is owned by the author, Cornel Huth, and is protected by United
States copyright laws and international treaty provisions. You are not
permitted to make copies of this software except for archival purposes.
7. You may not rent or lease BULLET. You may not transfer this license
without the written permission of the author. If this software is an
update or upgrade, you may not sell or give away previous versions.
8. You may not reverse engineer, decompile, or disassemble this software if
the intent or result is to alter the software.
9. There are no expressed or implied warranties with this software.
10. All liabilities in the use of this software rest with the user.
11. U.S. Government Restricted Rights. This software is provided with
restricted rights. Use, duplication, or disclosure by the Government is
subject to restrictions as set forth in subparagraph (c)(1)(ii) of the
Rights in Technical Data and Computer Software clause at 52.227-7013. The
software is owned by Cornel Huth/6402 Ingram Rd/San Antonio Texas
78238/USA. This agreement is governed by the laws of the Great State of
Texas, the United States of America, and all other countries of Earth.
Any questions concerning this License Agreement should be directed to Product
Support.
Failure to comply with any part of this License Agreement may result in
license revocation.
ΓòÉΓòÉΓòÉ 3. Installation ΓòÉΓòÉΓòÉ
Installation instructions are located in the README text file included with
your package.
ΓòÉΓòÉΓòÉ 4. Product Support ΓòÉΓòÉΓòÉ
Technical support in the use of Bullet is available for licensed users at the
40th Floor BBS or by way of the Internet.
40th Floor BBS:
+1(210)684-8065 N-8-1
Internet:
support@40th.com
http://www.40th.com
Response time is usually within 24 hours by internet e-mail or if you leave a
message at the support BBS. Alternatively, you may post a letter to Cornel
Huth/6402 Ingram Rd/San Antonio TX 78238/USA.
The latest in-version (2.x) release of BULLET 2 is available for download from
the BBS or from the internet at the www.40th.com site (alternate is
www.txdirect.net/~cornel/), or $5 by postal mail. Re-createable bugs are fixed
immediately. Contact support if you have any questions or requests.
For everyone else, or for general information, send e-mail to:
info@40th.com
ΓòÉΓòÉΓòÉ 4.1. Bug Report Form ΓòÉΓòÉΓòÉ
When requesting support for possible bug(s) use the following as a guideline:
1. Include a complete problem description.
2. Include sample source of the problem, if necessary (small is best).
3. Include necessary data files, include files, etc.
4. Include step-by-step procedure to follow in order to recreate the
problem.
Once done, send it to support by way of the BBS, e-mail, or postal mail to
addresses listed in Product Support.
ΓòÉΓòÉΓòÉ 5. Ordering Information ΓòÉΓòÉΓòÉ
To order Bullet by check, bank check, money-order, or cash, use form ORDER.FRM.
For credit card order, use form ORDER.CC.
Payment Options
Γûá Check, money-order, cash
Your funds have to be in US Dollars and, if by check, have to be drawn on or
payable through a US bank. If sending currency, use Registered AirMail.
Personal checks may require 10 working days to clear. Most major non-American
banks have branch banks in the US. Contact your bank for details. Direct
wire-transfer is available: enquire to sales@40th.com.
To order, send payment and the order form ORDER.FRM to:
Cornel Huth
6402 Ingram Rd
San Antonio, Texas 78238-3915
USA
Γûá Credit card, Eurochecks in DM, other
For credit card orders or for orders that cannot use the Direct-To-Author order
form, use the special Bullet/BMT Micro order form, or see the file !ORDER.CC in
the distribution package.
Delivery Options
Standard AirMail to all destinations is available at no extra charge. If a
printed manual is ordered, it will be AirMailed first-class.
Updates
Updates are available for $5 (to cover disk and shipping costs) for any 2.x
version update at your option level. Updates may be downloaded from the Bullet
support BBS or intenet free of charge
Note: All shipments outside of the US may go through your country's Customs.
Each package is valued at the total price of the order, less any shipping
costs, if any, unless pre-arranged otherwise. Your Customs Office may delay
mail delivery if duty is required.
Click here [Γûá] for the check, money-order, cash order form.
Click here [Γûá] for the credit card order from.
ΓòÉΓòÉΓòÉ 5.1. Order Form for Check, Money-order, or Cash ΓòÉΓòÉΓòÉ
Printing this section from here may not fit on a single page. Use the file
ORDER.FRM instead, or select the 'Services' menu item from here (upper-left),
and select 'Copy to file' to print this to .\TEXT.TMP.
Direct-To-Author Order Form (also see Credit Card Order Form)
Bullet 2.5 for OS/2 Order Form for Check, Money-order, or Cash
Cost Per Number of
ORDERING: BULLET 2.5 License Licenses Extended
----------------------------------- -------- --------
Bullet 2 (includes all platforms) $89.00 x =
----------------------------------- -------- ---------
=
Bullet 2 printed manual (OPTIONAL) $20.00 x ------- ---------
SUB-TOTAL = $
When ordering direct-to-author you ---------
save 15% on handling fees. Allow at
least 10 business days for your order 15% DISCOUNT = -
to be processed when using this form ---------
once it has been received.
BULLET 2 TOTAL = $
=========
Send TOTAL payment on check (US Dollars/US bank), money-order, or cash to
CORNEL HUTH Always endorse your letter
6402 INGRAM RD AirMail
SAN ANTONIO TX 78238-3915 if you are not in the USA
USA
PLEASE PRINT - For comments add a separate page and mark here [ ]
Your Name>
-------------------------------------------------- (required)
Company>
-------------------------------------------------- (optional)
Address>
-------------------------------------------------------------
-------------------------------------------------------------
-------------------------------------------------------------
E-mail> Disk
---------------------------------------------- Size> --------
Telephone Fax
Number> --------------------------- No.> ----------------------------
This form is not for credit cards Today's Date>
---------------------------------- ---/----/---
Check must be Payable from US Bank
ΓòÉΓòÉΓòÉ 5.2. Order Form for Credit Card ΓòÉΓòÉΓòÉ
Printing this section from here will not fit on a single page. Use the file
ORDER.CC instead, or select the 'Services' menu item from here (upper-left),
and select 'Copy to file' to print this to .\TEXT.TMP. Cut-and-paste as
needed.
To order Bullet by credit card (or Eurochecks in DM) use this form,
payable to BMT Micro. All other orders should use the Direct-To-
Author order form. All shipping is done by the author direct to
you, and is always the current release of Bullet. All Bullet 2
versions include a printed manual and include free shipping to all
destinations (AirMail).
Mail Orders To: BMT Micro
PO Box 15016
WILMINGTON NC 28408
USA
Voice Orders: 800AM - 700PM EST (-5 GMT)
(800) 414-4268 (orders only)
(910) 791-7052
Fax Orders: (910) 350-2937 24 hours / 7 Days
(800) 346-1672 24 hours / 7 Days
Online Orders via modem: (910) 350-8061 10 lines, all 14.4K
(910) 799-0923 Direct 28.8K line
Ordering and general ordering questions:
Via AOL: bmtmicro
via MSN: bmtmicro
Via Prodigy: HNGP66D
via Compuserve: 74031,307
via Internet: orders@bmtmicro.com
telnet@bmtmicro.com
www.bmtmicro.com
We accept Visa, Mastercard, Discover, American Express, Diners
Club, Carte Blanche, Cashiers Check, Personal Check. Personal
checks are subject to clearance. Eurochecks in DM are welcome.
DM, Sterling, and US Currency is welcome but send only by
registered mail, return reciept requested. We cannot be liable
for lost cash sent through the mail.
Purchase orders are welcome, subject to approval. The minimum
amount is $250.00.
Information for our German customers is explained in the last
paragraph of this order form.
-------------------------------cut here-----------------------------
Name:
------------------------------------------------------(required)
Company:
---------------------------------------------------(optional)
Address:
-------------------------------------------------------------
-------------------------------------------------------------
City: State/Province:
-------------------------------- -----------------
Postal/ZIP Code: Country:
--------------------- ------------------------
Phone:
---------------------------------------------------------------
Fax:
-----------------------------------------------------------------
E-Mail #1
------------------------------------------------------------
E-Mail #2
------------------------------------------------------------
Cost Per Number of
ORDERING: BULLET 2.5 License Licenses Extended
----------------------------------- -------- --------
Bullet 2 (includes all platforms) $89.00 x =
----------------------------------- -------- ---------
=
Bullet 2 printed manual (OPTIONAL) $20.00 x ------- ---------
SUB-TOTAL = $
---------
North Carolina Residents add 6% Sales Tax = +
---------
BULLET 2 TOTAL = $
=========
---------------------------------------------------------------------
| |
| For credit card payment only |
| |
| Circle one: VISA / Master / Discover / American Express / Diners |
| |
| Credit card number: |
| --------------------------------------------- |
| Expiration date: |
| ------------------------------------------------ |
| |
| Authorization signature: |
| ---------------------------------------- |
---------------------------------------------------------------------
-------------------------------cut here-----------------------------
ORDERING FROM INSIDE GERMANY ONLY
=================================
Persons in Germany wishing to order shareware may also transfer funds
into our account with Deutsche Bank. Once the money is deposited you
may either fax a confirmation to us with proof of deposit or wait until
Deutsche Bank notifies us of the transaction (usually 10-18 business days).
Account information is as follows:
Deutsche Bank / Frankfurt Branch
EmpfДnger: Thomas Bradford / BMT Micro
Konto-Nummer: 0860221
Bankleitzahl: 500-700-10
When you make the transfer, be sure to put your name and the program you
are registering on the transfer.
Current exchange rates can be obtained by sending an email to
dm_to_us@bmtmicro.com. An automated reply will return todays exchange
rates.
It is very important that you send us a completed order form by
either email or fax if you deposit money into this account for a
registration. Fill the order form out as usual except in the credit
card number field put "DEUTSCHE BANK". We will file the order and
use it to match against the deposit information we receive from the
bank.
IMPORTANT!
----------
When you email us your order form, we will reply with an
acknowledgement. If you do not get an acknowledgement within 24 hours
please send your order again in case it was lost. This extra bit of
caution can save a lot of confusion.
If you are concerned that your order is taking too long to process, feel
free to check with us about the status of your order. It's important
to all of us that you feel safe doing business with our company and
please feel free to suggest ways we can improve our service to you.
ΓòÉΓòÉΓòÉ 6. Tutorial ΓòÉΓòÉΓòÉ
This tutorial describes how to set up a basic database. It describes file
layout and how to use Bullet to enter into and read from the database. It does
not describe aspects that do not relate to Bullet, such as how to load list
boxes, create dialogs, etc. This tutorial creates a music CD collection
database.
The tutorial follows.
ΓòÉΓòÉΓòÉ 6.1. Tutorial: Output Requirements ΓòÉΓòÉΓòÉ
The goal of this tutorial is to create a database for a music CD collection.
The method used is a suggested method; it is by no means the only way to
develop a database, and only touches the surface of database programming. That
said...
The most important thing to do is to know what output you want. From that, you
know what input is needed. For the 'CD database', I've drawn up a list of
things that I want:
Title, artist, and year. Must have those. Track name (the song name), track
number, and play time would be essential, too. How about who was playing on
the track and instruments they used? Put that on your list -- it's not on this
one.
All these could be entered into a single file as a single database record, but
that would be a pretty poor design. A single data file may seem to be the
obvious way, but in the long run, it's not very good at all. This database
uses two data files. This concept -- multiple data files -- is especially
useful when the database is more complex. For example, an accounting system
would employ many physical data files. It could be done in one, sure, but the
maintenance would be very difficult.
Two tables are used: The Title-Artist-Year table (Title/CTAY), and the
TrackNumber-TrackName-Time table (Tracks/CTTT). A table is a file composed of
rows and columns. A row is a record; a column is a field. No two rows may be
exactly alike (what would be the point?). In addition to the fields listed
(the Title, Artist, and Year, for instance), each table includes a Code field.
TrackNumber is included as a field value in the second table since the physical
ordering of rows cannot be used for this purpose (it could, technically, but it
is unwise to depend on row order being preserved since there is nothing
enforcing the physical ordering of records).
The Code field for the Title table (CTAY) is generated by the program and is
composed of the first 4 characters of the Title, plus the first 4 characters of
the Artist, plus the year (all four characters of it). This code is also used
in the Tracks table (CTTT). This serves two purposes: First, it ensures that
each Track record is unique (with pretty good certainty; without it, it is
possible that TrackNumber-TrackName-Time alone may occur more than once), and
second, it can be used as the primary key when being accessed through the
foreign key in the Title table (see Record Layout Diagrams). By searching CTAY
(the Title Table) for a desired title, or artist, or even year, and using the
Code field from that row, you can match it to the Code in CTTT (Track Table) to
find the tracks that belong to that title row (or artist, or year -- whatever
the search was based on). To speed up searching, indexes are maintained and
used for lookups. For a very large CD collection (say, several thousand CD
titles, and tens of thousands of tracks), the database can still be very
quickly queried for whatever information you need (and have programmed).
ΓòÉΓòÉΓòÉ 6.2. Tutorial: Record Layout ΓòÉΓòÉΓòÉ
The Code field is generated by your program. Other fields are input data. The
following are the data record layouts of the DBF files (tag fields not shown).
The time field may be created as a text field with the form "mm:ss", or could
even be made into a 'seconds' field. This database uses the more direct
"mm:ss" since that's how it's read off the CD. Note that this information is
already stored digitally on the CD itself (running time), but that would
require additional coding and is not relevant to this tutorial (but oh, how
sweet it could be).
CTAY TABLE
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé CODE Γöé TITLE Γöé ARTIST ΓöéYEAR Γöé
Γöé Γöé Γöé Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
MexiConc1993 Mexican Moon Concrete Blonde 1993
No NCran1994 No Need to Argue Cranberries, The 1994
MachDeep1972 Machine Head Deep Purple 1972
CTTT TABLE
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé CODE ΓöéTRKΓöé TRACKNAME ΓöéTIME Γöé
Γöé Γöé Γöé Γöé Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
MexiConc1993 01 Jenny I Read 05:18
MexiConc1993 02 Mexican Moon 05:03
MexiConc1993 03 Heal It Up 04:21
MexiConc1993 04 Jonestown 06:06
MexiConc1993 05 Rain 03:28
MexiConc1993 06 I Call It Love 05:15
MexiConc1993 07 Jesus Forgive Me 05:17
MexiConc1993 08 When You Smile 04:18
MexiConc1993 09 Close To Home 03:31
MexiConc1993 10 One Of My Kind 03:55
MexiConc1993 11 End Of The Line 04:39
MexiConc1993 12 Blind Ambition 06:10
MexiConc1993 13 Bajo La Lune 05:07
No NCran1994 01 Ode To My Family 04:30
No NCran1994 02 I Can't Be With You 03:07
No NCran1994 03 Twenty One 03:08
No NCran1994 04 Zombie 05:06
No NCran1994 05 Empty 03:26
No NCran1994 06 Everything I Said 03:53
No NCran1994 07 The Icicle Melts 02:54
No NCran1994 08 Disappointment 04:14
No NCran1994 09 Ridiculous Thoughts 04:31
No NCran1994 10 Dreaming My Dreams 03:37
No NCran1994 11 Yeat's Grave 02:59
No NCran1994 12 Daffodil Lament 06:09
No NCran1994 13 No Need To Argue 02:56
MachDeep1972 01 Highway Star 06:05
MachDeep1972 02 Maybe I'm A Leo 04:51
MachDeep1972 03 Pictures Of Home 05:03
MachDeep1972 04 Never Before 03:56
MachDeep1972 05 Smoke On The Water 05:40
MachDeep1972 06 Lazy 07:19
MachDeep1972 07 Space Truckin' 04:31
ΓòÉΓòÉΓòÉ 6.3. Tutorial: Index Files Used ΓòÉΓòÉΓòÉ
The index files created for this database are, for CTAY: Title+Artist and
Artist+Title (the + indicates a compound key made up of more than one field);
for CTTT: Code+TrackName and TrackName+Code. With these indexes, searching can
be made on CD title, by Artist, or by TrackName. If searching by year is
required, an additional index could be used, such as Year+Artist+Title (Title
being used as part of the key in case an Artist releases more than one title in
a year).
To search by CD title, a title is used as the key. Even a partial title can be
used during the actual lookup. For example, 'Mexican'. From this, a search is
made in the Title+Artist index. The first key starting with 'Mexican' is
returned. The index can then be traversed to find any other keys also starting
with this (by getting the next key). Since Bullet has high-level access
routines (GET_FIRST_XB, for example), where a key search returns the data
record, once a key is found its data is already in memory (the record is
typically read directly into a structure variable you have set up). This lets
you move through the CTAY file getting each record in key order, one at a time
(one key and record per call to Bullet).
With the data record in memory, the Code field in the record (CTAY table) is
used as a foreign key into CTTT. A new search needs to be done, this time on
the Code+TrackName index of the CTTT table. With the search key set to
'MexiConc1993' (the foreign key field in CTAY of the record that first matched
'Mexican'), the Code+TrackName index for CTTT is searched. The first key
starting with 'MexiConc1993' is returned, and its data record. In that record
is the track name, track number, and track time. When done with that record,
the index can be tranversed in-order (forward, or even backward), and, so long
as the first 12 characters of the Code+TrackName key match 'MexiConc1993', that
key's record is used. When the match is no longer true in those first 12
characters, which in the example data occurs after 13 tracks have been read, it
means that all matching keys in that index file have been found, and there are
no others matching the criterion.
The TrackName part of Code+TrackName (the part of the key after the first 12
characters) is not needed as a match criterion in this case; TrackName is being
used solely for the purpose of making the key unique to that particular CD
Title (all Code fields are the same for each trackname on the CD).
A possible additional index file would be a key based on Code, for CTAY (there
is such an index already for CTTT). This would allow indexed access to the
CTAY table when looking for the Title of a track name. (In other words, you
have a list of track names of all CDs in the database, in track name order, and
want to know the CD Title for each track you see.) Even without an index this
can be done by using the Code value in CTTT as a sequential search lookup value
in CTAY (searching for the matching Code value in CTAY). This additional index
is not used here.
ΓòÉΓòÉΓòÉ 6.4. Tutorial: Creating the Data Files ΓòÉΓòÉΓòÉ
Now that the output requirements are known, and the layout of the data records
of the two data files constructed, and the index access methods designed, the
database can be created. Bullet provides routines to create the files of the
database according to specifications that you supply.
The CTAY (Code-Title-Artist-Year) and CTTT (Code-Track-TrackName-Time) tables
are to be in this format:
CTAY TABLE
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé CODE Γöé TITLE Γöé ARTIST ΓöéYEAR Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
MexiConc1993 Mexican Moon Concrete Blonde 1993 (sample record)
:
CODE character data, 12 bytes
TITLE character data, 32 bytes
ARTIST character data, 35 bytes
YEAR character data, 4 bytes
CTTT TABLE
ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö¼ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ
Γöé CODE ΓöéTRKΓöé TRACKNAME ΓöéTIME Γöé
ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓö┤ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ
MexiConc1993 01 Jenny I Read 05:18 (sample record)
:
CODE character data, 12 bytes
TRK character data, 2 bytes
TRACKNAME character data, 40 bytes
TIME character data, 5 bytes
The fields are sized primarily on expected need. Since each DBF record has a
1-byte tag field, the sum of the above record layouts, including the tag byte,
is aligned to an even 4-byte size. An admirable goal, but not a primary design
consideration. CTAY is 84 bytes per record; CTTT is 60 bytes per record.
Data is not entered during the create process. That comes later. The create
is only to make the physical files. The following is the Bullet code required
to build the two data files. The index files are covered later.
typedef struct _CTAY {
CHAR tag;
CHAR code[12];
CHAR title[32];
CHAR artist[35];
CHAR year[4];
} CTAY; // (total CTAY record length is 84 bytes)
CTAY ctayRec;
CHAR ctayName[] = "CTAY.DBF";
ULONG ctayID=0; // handle of CTAY file
FIELDDESCTYPE ctayFieldList[4]; // 4 fields used by the record
memset(ctayFieldList,0,sizeof(ctayFieldList)); // init unused bytes to 0
typedef struct _CTTT {
CHAR tag;
CHAR code[12];
CHAR trk[2];
CHAR trackName[40];
CHAR time[5];
} CTTT; // (total CTTT record length is 60 bytes)
CTTT ctttRec;
CHAR ctttName[] = "CTTT.DBF";
ULONG ctttID=0;
FIELDDESCTYPE ctttFieldList[4];
memset(ctttFieldList,0,sizeof(ctttFieldList));
// field descriptor info for CTAY
strcpy(ctayFieldList[0].fieldName, "CODE");
ctayFieldList[0].fieldType = 'C';
ctayFieldList[0].fieldLen = 12;
ctayFieldList[0].fieldDC = 0;
strcpy(ctayFieldList[1].fieldName, "TITLE");
ctayFieldList[1].fieldType = 'C';
ctayFieldList[1].fieldLen = 32;
ctayFieldList[1].fieldDC = 0;
strcpy(ctayFieldList[2].fieldName, "ARTIST");
ctayFieldList[2].fieldType = 'C';
ctayFieldList[2].fieldLen = 35;
ctayFieldList[2].fieldDC = 0;
strcpy(ctayFieldList[3].fieldName, "YEAR");
ctayFieldList[3].fieldType = 'C';
ctayFieldList[3].fieldLen = 4;
ctayFieldList[3].fieldDC = 0;
// field descriptor info for CTTT
strcpy(ctttFieldList[0].fieldName, "CODE");
ctttFieldList[0].fieldType = 'C';
ctttFieldList[0].fieldLen = 12;
ctttFieldList[0].fieldDC = 0;
strcpy(ctttFieldList[1].fieldName, "TRACK");
ctttFieldList[1].fieldType = 'C';
ctttFieldList[1].fieldLen = 2;
ctttFieldList[1].fieldDC = 0;
strcpy(ctttFieldList[2].fieldName, "TRACKNAME");
ctttFieldList[2].fieldType = 'C';
ctttFieldList[2].fieldLen = 40;
ctttFieldList[2].fieldDC = 0;
strcpy(ctttFieldList[3].fieldName, "TIME");
ctttFieldList[3].fieldType = 'C';
ctttFieldList[3].fieldLen = 5;
ctttFieldList[3].fieldDC = 0;
// create the CTAY file
CDP.func = CREATE_DATA_XB;
CDP.filenamePtr = ctayName;
CDP.noFields = 4;
CDP.fieldListPtr = ctayFieldList;
CDP.fileID = 3;
rez = BULLET(&CDP);
if (rez) {
printf("Failed CTAY create. Err: %d\n",rez);
return(rez);
}
// create the CTTT file
CDP.func = CREATE_DATA_XB;
CDP.filenamePtr = ctttName;
CDP.noFields = 4;
CDP.fieldListPtr = ctttFieldList;
CDP.fileID = 3;
rez = BULLET(&CDP);
if (rez) {
printf("Failed CTTT create. Err: %d\n",rez);
return(rez);
}
ΓòÉΓòÉΓòÉ 6.5. Tutorial: Opening the Data Files ΓòÉΓòÉΓòÉ
Before using a data file it must be opened. Any DBF file can be opened,
whether created by Bullet or another program. Before creating indexes for the
data files created here, the data files must be open. The handles of the open
data files are used in the index file create process.
// open CTAY and store handle to ctayID
OP.func = OPEN_DATA_XB;
OP.filenamePtr = ctayData;
OP.asMode = READWRITE | DENYNONE;
rez = BULLET(&OP);
if (rez) {
printf("Failed CTAY file open. Err: %d\n",rez);
return(rez);
}
ctayID = OP.handle;
// open CTTT and store handle to ctttID
OP.func = OPEN_DATA_XB;
OP.filenamePtr = ctttData;
OP.asMode = READWRITE | DENYNONE;
rez = BULLET(&OP);
if (rez) {
printf("Failed CTTT file open. Err: %d\n",rez);
return(rez);
}
ctttID = OP.handle;
ΓòÉΓòÉΓòÉ 6.6. Tutorial: Creating the Index Files ΓòÉΓòÉΓòÉ
Two pairs of index files are used. For CTAY, Title+Artist and Artist+Title;
for CTTT, Code+TrackName and TrackName+Code. These are expected to be unique,
and so the index files are specified to not accept duplicate keys. Also,
rather than using the full field sizes for key expressions, substrings are
specified. Long keys affect performance, both in index file size and general
access speed (smaller is usually better), and using the entire field data does
little to differentiate the key (for the uniqueness requirement). If
DUPS_ALLOWED were specified, even shorter keys could be used, though duplicates
generally are not desirable. If there comes a record whose generated key is
not unique (i.e., it already belongs to another already-added record), either
modify the data entered so that its key is different, if possible, or redo your
index files to allow duplicates. You could also play if 'safe' and permit
duplicates keys to occur from the outset, knowing that not many duplicates will
occur, where if they do, you can continue and let Bullet managed the duplicate
key with an enumerator. If many duplicates of keys are possible, re-evaluate
your key expression to make it more unique. This is done by using a compound
key with more field components. Still, the fewer components in a key, and the
shorter, the better the key is.
Index file creation:
CHAR xTitleName[] = "TITLE.IX3";
CHAR xTitleExp[] = "SUBSTR(TITLE,1,8)+SUBSTR(ARTIST,1,8)";
CHAT xTitleBuffer[16]; // key buffer for later use
// note that if DUPS_ALLOWED, the key buffer must
// provide room for the enumerator (2 bytes more)
CHAR xArtistName[] = "ARTIST.IX3";
CHAR xArtistExp[] = "SUBSTR(ARTIST,1,8)+SUBSTR(TITLE,1,8)";
CHAT xArtistBuffer[16];
CHAR xCodeName[] = "CODETRK.IX3";
CHAR xCodeExp[] = "CODE+SUBSTR(TRACKNAME,1,12)";
CHAT xCodeBuffer[24];
CHAR xTrackName[] = "TRKNAME.IX3";
CHAR xTrackExp[] = "SUBSTR(TRACKNAME,1,12)+CODE";
CHAT xTrackBuffer[24];
// create two index files for CTAY
CIP.func = CREATE_INDEX_XB;
CIP.filenamePtr = xTitleName;
CIP.keyExpPtr = xTitleExp;
CIP.xbLink = ctayID; // the handle of the open CTAY file
CIP.sortFunction = NLS_SORT; // sort key by NLS for proper mixed-case order
CIP.codePage = 0; // use OS-default code page
CIP.countryCode = 0; // use OS-default country code
CIP.collatePtr = NULL; // no need for a special collate table
CIP.nodeSize = 512; // 512-byte node size (or 1024, 2048 bytes)
rez = BULLET(&CIP);
if (rez) {
printf("Failed Title index create. Err: %d\n",rez);
return(rez);
}
CIP.filenamePtr = xArtistName;
CIP.keyExpPtr = xArtistExp; // other values still valid from above
rez = BULLET(&CIP);
if (rez) {
printf("Failed Artist index create. Err: %d\n",rez);
return(rez);
}
// create two index files for CTTT
CIP.func = CREATE_INDEX_XB;
CIP.filenamePtr = xCodeName;
CIP.keyExpPtr = xCodeExp;
CIP.xbLink = ctttID; // as above...
CIP.sortFunction = NLS_SORT;
CIP.codePage = 0;
CIP.countryCode = 0;
CIP.collatePtr = NULL;
CIP.nodeSize = 512;
rez = BULLET(&CIP);
if (rez) {
printf("Failed Code index create. Err: %d\n",rez);
return(rez);
}
CIP.filenamePtr = xTrackName;
CIP.keyExpPtr = xTrackExp; // other values still valid from above
rez = BULLET(&CIP);
if (rez) {
printf("Failed TrackName index create. Err: %d\n",rez);
return(rez);
}
ΓòÉΓòÉΓòÉ 6.7. Tutorial: Opening the Index Files ΓòÉΓòÉΓòÉ
Before using an index file it must be opened. Only Bullet index files can be
opened.
// open Title index and store handle to xTitleID
OP.func = OPEN_INDEX_XB;
OP.filenamePtr = xTitleName;
OP.asMode = READWRITE | DENYNONE;
OP.xbLink = ctayID; // note xbLink
rez = BULLET(&OP);
if (rez) {
printf("Failed Title index open. Err: %d\n",rez);
return(rez);
}
xTitleID = OP.handle;
// open Artist index and store handle to artistID
OP.func = OPEN_INDEX_XB;
OP.filenamePtr = xArtistName;
OP.asMode = READWRITE | DENYNONE;
OP.xbLink = ctayID;
rez = BULLET(&OP);
if (rez) {
printf("Failed Artist index open. Err: %d\n",rez);
return(rez);
}
xArtistID = OP.handle;
// open Code index and store handle to xCodeID
OP.func = OPEN_INDEX_XB;
OP.filenamePtr = xCodeName;
OP.asMode = READWRITE | DENYNONE;
OP.xbLink = ctttID;
rez = BULLET(&OP);
if (rez) {
printf("Failed Code index open. Err: %d\n",rez);
return(rez);
}
xCodeID = OP.handle;
// open TrackName index and store handle to xTrackNameID
OP.func = OPEN_INDEX_XB;
OP.filenamePtr = xTrackName;
OP.asMode = READWRITE | DENYNONE;
OP.xbLink = ctttID;
rez = BULLET(&OP);
if (rez) {
printf("Failed TrackName index open. Err: %d\n",rez);
return(rez);
}
xTrackNameID = OP.handle;
ΓòÉΓòÉΓòÉ 6.8. Tutorial: Inserting CD Title Data ΓòÉΓòÉΓòÉ
The files have been created, and are open. Data may now inserted into the
database. The term 'inserted' is used in contrast to 'added' since Bullet
'adds' data only, while it 'inserts' data and key information together (and
especially, the key is inserted, in order). The CD data entry would typically
be done via the keyboard. This tutorial assumes that this has already been
done, and that the data items are in the following structure variables:
ctayRec for CD title data; ctttRec for track data.
First, the CD title record is added to CTAY with a key inserted into the Title
index and another key into Artist index. Data entered by the user consists of
CD title, artist, and year. The Code field is generated by the program code,
shown below.
// ctayRec has CD title, artist, and year -- generate Code field data
strncpy(ctayRec.code ,ctayRec.title,4); // first 4 of title
strncpy(ctayRec.code+4,ctayRec.artist,4); // first 4 of artist
strncpy(ctayRec.code+8,ctayRec.year,4); // and 4 of year make the Code field
// insert CD title data (one data record added, two index files inserted into)
AP[0].func = INSERT_XB;
AP[0].handle = xTitleID; // handle of Title index
AP[0].recNo = 0; // required
AP[0].recPtr = &ctayRec; // the CD title record
AP[0].keyPtr = xTitleBuffer; // key buffer
AP[0].nextPtr = &AP[1]; // since two index files, point to next
AP[1].handle = xArtistID: // handle of Artist index
AP[1].recNo = 0x80000000; // required
AP[1].recPtr = &ctayRec; // the CD title record
AP[1].keyPtr = xArtistBuffer; // key buffer
AP[1].nextPtr = NULL;
rez = BULLET(&AP[0]);
if (rez) { // rez is not error, but pack index of error
rc = AP[abs(rez)-1].stat;
printf("Insert failed, pack: %d, err: %d\n",rez,rc);
return(rc);
}
A CD title has been entered. That's the first part of the data entry. Next,
each track on that CD needs to be entered. It needs the Code field data
generated above, along with the general track data (track name, etc.).
ΓòÉΓòÉΓòÉ 6.9. Tutorial: Inserting CD Track Data ΓòÉΓòÉΓòÉ
During the CD title insert, a Code field value was generated. That field value
is used for each of the track entries inserted into database for this CD. The
actual track data has already been placed in the ctttRec structure variable (by
the main program code, not shown here), and only the Code field of ctttRec
needs to be set. Once done, the track record is inserted, this time into the
CTTT data file and the Code and Track index files.
//
// this code section is repeated for each track on the CD
//
// copy Code field data from CTAY
strncpy(ctttRec.code,ctayRec.code,12); // use Title's Code for each Track entry
// insert track data (one data record added, two index files inserted into)
AP[0].func = INSERT_XB;
AP[0].handle = xCodeID; // handle of Code index
AP[0].recNo = 0; // required
AP[0].recPtr = &ctttRec; // the track record
AP[0].keyPtr = xCodeBuffer; // key buffer
AP[0].nextPtr = &AP[1]; // since two index files, point to next
AP[1].handle = xTrackID: // handle of TrackName index
AP[1].recNo = 0x80000000; // required
AP[1].recPtr = &ctttRec; // the track record
AP[1].keyPtr = xTrackBuffer; // key buffer
AP[1].nextPtr = NULL;
rez = BULLET(&AP[0]);
if (rez) { // rez is not error, but pack index of error
rc = AP[abs(rez)-1].stat;
printf("Insert failed, pack: %d, err: %d\n",rez,rc);
return(rc);
}
// and repeat for each track on the CD
The database now contains a complete entry for the one CD. This process is
repeated for each CD that is to entered into the database. This is the data
entry process. Data retrieval is covered next.
ΓòÉΓòÉΓòÉ 6.10. Tutorial: Retrieving Data ΓòÉΓòÉΓòÉ
The database can be accessed in several different ways. For indexed access,
any index file can be used. Alternatively, direct access without an index
could also be used. Generally, since there's no guarantee that the physical
order of rows in the database is in any order at all, index access is usually
desired. Possible retrievals are by:
1. CD title, in alphabetical order, and, from information from the CD title,
all tracks on the CD. The tracks are read into an array and sorted by track
number. Alternatively, a separate index could be used to order track entries
by TrackNo+TrackName(+etc.), but since there are so few tracks per CD
(generally less than 20), it's much more efficient to simply get all tracks for
a CD title into an array, and then sort that array for presentation.
Otherwise, an index file would need to be maintained on a permanent basis, and
for something that can easily be done at run-time (and then discarded).
2. Artist, in alpha order. Access is similar to CD title, above, since it uses
the same data file (CTAY). As above, tracks for each artist's title can then
be retrieved. The only difference between this and the CD title access is that
this lists CDs by artist, rather than by title.
3. Track name, in alphabetical order. This lists all tracks by track name, in
order, with different tracks intermixed with CD titles. In other words, if
three CDs had the track called "In the Summer Time", then each of those tracks
would appear together, one after the other. There is no index available in
this database to access the CD on which the track name appears, so you cannot
go from trackname alone to CD title (by index). However, since the Code field
is available, and is common to each data file, the Code field value of a track
record can be used to sequentially search the Title records for a match. This
is a non-indexed search. Access is slower than if an index were available, but
unless you created indexes for all possible search methods, some sequential
(read: slow) searching may be required. For smaller files (less than 10,000
records), or where only a few such searches occur, this may be perfectly
acceptable.
An index on Code exists (for CTTT, but not CTAY), and you may find a use for
this other than for use as a foreign key lookup. Also, you may create ad hoc
index files whenever you need to, and these can be deleted when no longer
required. Since Bullet can create an index for even a large file in just a few
seconds, this is a viable option; the few seconds needed to create an index may
be many times repaid in the time saved in sequential searches on a non-indexed
access.
The first retrieval mentioned is shown next.
// for each CD title in the database, display all its tracks
// ctayRec already defined
CTTT cttyRecs[59]; // store each Track record for later sorting by track#
// This would be repeated for each CD title (only the first is shown).
// Note: For the next CD title, GET_NEXT_XB would be used. If necessary,
// the key just accessed could be stored (preserved), then laster reaccessed
// using GET_EQUAL_XB again, and then immediately followed with a GET_NEXT_XB.
// This way, you can stop processing completely, and restart up where you left
// off.
// files are locked as required (e.g., full-lock, shared)
AP.func = GET_FIRST_XB;
AP.handle = xTitleID;
AP.recPtr = &ctayRec;
AP.keyPtr = xTitleBuffer;
rez = BULLET(&AP);
while (rez==0) {
printf("Title: %s Artist: %s Year: %s\n",
ctayRec.title,
ctayRec.artist,
ctayRec.year);
// get each track belonging to this CD title (same Code values)
int trk=0; // counter
memset(xCodeBuffer,0,sizeof(xCodeBuffer)); // clear it to 0
strncpy(xCodeBuffer,ctayRec.code,12); // copy Code to search-for buffer
AP.func = GET_EQUAL_XB;
AP.handle = xCodeID;
AP.recPtr = &ctttRecs[trk];
AP.keyPtr = xCodeBuffer; // find Code
rez = BULLET(&AP);
// rather than print each when gotten, collect them, sort, then display
// if rez==0 here then Code matched exactly, no strncmp needed, otherwise,
// there's no match on the first 12 characters of Code
AP.func = GET_NEXT_XB; // continue getting while same Code
while (rez==0) {
trk++; // limit to array size...
AP.recPtr = &ctttRecs[trk]; // read into next array record
rez = BULLET(&AP);
if (rez==0) {
// if code field in CTTT no longer the same as CTAY, then have them all
if (strncmp(ctttRecs[trk].code,ctayRec.code,12)!=0) {
rez = 1;
break; // different Code fields
}
}
}
// if rez==1 or at end of file then no error, sort and display --
// trk at this point is number of tracks that matched Code (1-based)
// DoSortOf... routine sorts the ctttRecs array with trk count of elements
if ((rez==1) | (rez==EXB_END_OF_FILE)) {
DoSortOfTheTrackRecordsByTrackNumber(&ctttRecs[0],trk);
// print each track record, now sorted by track number
for (i=0;i < trk;i++) {
printf("Trk: %s Name: %s Time: %s\n",
ctttRecs[i].trk,
ctttRecs[i].trackName,
ctttRecs[i].time);
}
else {
printf("failed, rez: %d\n",rez);
return(rez);
}
}
ΓòÉΓòÉΓòÉ 6.11. Tutorial: Preparing to End Your Program ΓòÉΓòÉΓòÉ
It's proper programming practice to perform an orderly shutdown of Bullet when
ending your program. This also extends to files, too, where even if you are
not ending your program, but are finished using a file, close that file so that
you release the resources allocated for it.
In the event that an abnormal termination occurs, and you are unable to release
outstanding locks and close all files, you may choose to call EXIT_XB and allow
that to close files (the OS releases locks when the file is closed). If you
are unable to call even EXIT_XB, there is still another method that gets
Bullet's attention. During Bullet initialization, the EXIT_XB routine is
registered with the OS so that whenever your program terminates (or is
terminated), EXIT_XB is called automatically. This ensures that headers are
properly written in all but the most severe of abnormal terminations (abend),
such as a lockup.
// all outstanding locks should have been released immediately after use
// close each file that was opened
HP.func = CLOSE_DATA_XB
HP.handle = xTitleID;
if (HP.handle) rez = BULLET(&HP);
if (rez) printf("close failed, err: %d\n",rez);
HP.handle = xArtistID;
if (HP.handle) rez = BULLET(&HP);
if (rez) printf("close failed, err: %d\n",rez);
HP.handle = xCodeID;
if (HP.handle) rez = BULLET(&HP);
if (rez) printf("close failed, err: %d\n",rez);
HP.handle = xTrackID;
if (HP.handle) rez = BULLET(&HP);
if (rez) printf("close failed, err: %d\n",rez);
EP.func = EXIT_XB;
rez = BULLET(&EP);
if (rez) printf("exit failed, err: %d\n",rez);
// Bullet is now deinitialized, and must be INIT_XB'ed before further use
ΓòÉΓòÉΓòÉ 7. History of Changes ΓòÉΓòÉΓòÉ
Changes Made
2.50 25-Jun-97:
1. Modifed Win32 DLL to work in NT4.
2. Changed the way a Next/Prev key access works after reaching EOF/TOF.
E.g., now when EOF is reached a Prev key access will get the second to
last key, in order, rather than re-getting the last key.
3. Fixed when using ATOMIC_MODE=1 in SET_SYSVARS_XB, and you have
DUPS_ALLOWED allowed, when using INSERT_XB, UPDATE_XB, or LAST_KEY_XB,
where error EXB_KEY_NOT_FOUND (8501) was returned when the key newly
inserted/updated already existed.
4. Fixed PREV_KEY/GET_PREV_XB where only the first encounter of
EXB_TOP_OF_FILE was returned, after which out-of-order key was returned.
2.14 29-May-97:
1. Fix for key order bug dropping to ASCII sort even when ASCII sort was not
picked.
2.13 23-Nov-96:
1. Added EQUAL_OR_GREATER_KEY/LESSER_KEY for fuzzy key access (atomic).
2. Added GET_EQUAL_OR_GREATER/LESSER for fuzzy key+record access (atomic).
3. Increased maximum fields from 255 to 1024 for dBASE 5 DBFs.
4. Update of data record would not set DBF dirty flag, though effect was
minimal (last update timestamp in DBF header not set).
5. Fixed Bullet/2 REXX interface DLL dispatcher.
2.12 6-Nov-96:
1. New, lower prices! Order direct-from-author or from BMT Micro.
2. New source example, compact.c, to compact DBFs with attached DBT memo
file.
3. Release 2.12. Code version is 2.11.
2.11 16-Oct-96:
1. New order option, option E, includes DOSX32, OS/2 and Win32 versions of
Bullet all in one box.
2.105 11-Oct-96:
1. Added OS function override revectoring for all OS calls made, and added
supporting query/set routines, QUERY/SET_VECTORS_XB.
2. Added USE_ANSI_SET flag for index file collate sequence table (esp. for
Windows), in addition to standard OEM character set, for use at
CREATE_INDEX_XB.
3. Added shared/exclusive region locking mode for NT.
4. UPDATE_XB efficiency improved, esp. with large number of index files.
5. Internal sort efficiency improved (even faster).
6. Improved demo program samples; also added compile option for generating
console or windowed app.
7. Consolidated Bullet header files into one, for all platforms, with
minimal conditionals; the file is named bullet_2.h.
2.103 4-Oct-1996:
1. Win32 version would fail under NT (and Win32s) because of un-saved
register into consecutive kernel calls.
2. Win32s now supported.
3. Win32 version now in a single DLL (supports Watcom (old and new) and VC4,
both, and both Win32 and Win32s).
4. CREATE_INDEX_XB was always using OS-supplied codepage/country code.
5. Added callback support in reindex and pack records, esp. useful for
DOSX32 and Win32s.
6. UPPER() in key expression would fail.
2.101 28-Aug-1996:
1. REINDEX_XB would fail on certain key sizes, causing an access
violation/GPF.
2.100 3-Aug-1996:
1. REINDEX_XB and GET_LAST_XB could fail if DUPS_ALLOWED.
2. Sort functions 1, 2, and 3 were operating as expected but 4-6 were using
3.
3. Node scanning was using the wrong sort-compare function (1 was using 3, 2
was using 1, and 3 was using 2).
4. UPDATE_XB was returning failed pack's AP[].stat=1 instead of true error
code if the update failed when inserting a new key.
5. BUILD_KEY_XB was not issuing skip tag warning, and was not passing
.recPtr to external build key function.
6. PACK_RECORDS_XB and DEBUMP_RECORD_XB values in bullet2.h were
interchanged: Pack is 47; Debump is 46.
7. DEBUMP_RECORD_XB requires FLUSH_DATA_HEADER_XB call prior to use.
8. Documentation errata: GET_DESCRIPTOR_XB uses DP.handle for IN; includes
DP.FD.altFieldLength for OUT (DP.fieldOffset noted previously)
9. GET_DESCRIPTOR_XB was not returning info in DP.FD.* when accessed by
fieldName.
10. SDP.lastUpdate year was returned with year-256 (1996 was 1740).
11. GET_ERROR_CLASS_XB was not returning useful info.
12. ZAP_INDEX_HEADER_XB had been making index data start at 16KB size.
13. DosExitList() handler is now dropped after calling EXIT_XB.
2.051 24-Apr-96:
1. Fixes to transaction routines InsertXB and UpdateXB, including Insert
rollback fix with multiple packs.
2. Update pack number return value fix.
3. Update w/ multiple packs fix, and an Update rollback fix.
4. Fix of GetKeyForRecordXB when DUPS_ALLOWED.
2.050 26-Feb-96:
1. Shareware refresh released.
2.044 31-Jan-96:
1. Fixed DUPS_ALLOWED bug at INSERT_XB where error EXB_TOP_OF_FILE or error
EXB_TOO_MANY_DUPLICATES would be returned.
2.043 3-Jan-96:
1. Added atomic key access for NEXT_KEY, PREV_KEY, GET_NEXT, and GET_PREV
for simpler use in multi-threaded programs.
2. Changed EXB_SHARED_LOCK_ON in BULLET2.H (was ERR_ rather than EXB_).
2.042 20-Dec-95:
1. MAKE_DIR_DOS was expecting directory name in DFP.bufferPtr. Changed to
expect path in DFP.filenamePtr.
2.040 28-Oct-95:
1. BACKUP_FILE_XB extended to back up related memo file on DBF backup.
2. [2.033] Fixed memo file problems.
3. [2.032] Win95 version available.
4. [2.031] DOSX32 version available.
2.031 11-Sep-95:
1. Added DP.fieldOffset to documentation for GET_DESCRIPTOR_XB.
2. Instance tracker purified of DosEnterCritSec need.
2.030 9-Sep-95:
1. Removed erroneous '...header reload...' in online docs of RELOCK_DATA_XB.
2. Default maximum filesizes set to 2047 MB from 2048 MB (absolute max is
4095 MB).
3. Fixed REINDEX_XB so that it actually uses re-evaluted key expression.
4. ATEXIT_XB is obsolete.
5. BREAK_XB is obsolete.
2.020 31-Aug-95:
1. Locality and other cache-related options can be set at file open.
2. IP.versionBullet changed to *1000 from *100 (2020 is version 2.020).
3. Instance tracker corrected.
2.01 22-Aug-95:
1. Changed index header to 1024 bytes from 768 for sector-alignment.
2. Changed header ID to '31ch' from '30ch'.
2.00 20-Aug-95:
Preliminary release.
ΓòÉΓòÉΓòÉ 8. Bullet Include File ΓòÉΓòÉΓòÉ
The Bullet/2 C/C++ header file, BULLET_2.H:
/* BULLET_2.H 23-Nov-96-chh
*
* Bullet header for 32-bit C/C++ (DOSX32, OS/2, and Win32s/Win32)
* Bullet call numbers, parameter packs, and error number equates
*
* Requires PLATFORM defined and set to ON_DOSX32 (3), ON_OS2 (4),
* or ON_WIN32 (5) before getting here. For example:
* #define PLATFORM ON_DOSX32 (ON_DOSX32 defined as 3)
*
*/
#ifndef __BULLET_H
#define __BULLET_H
/*
* The #pragma pack(1)/#pragma pack() is no longer required since all
* structure members in this header will align properly -- all are
* 32-bit size except for the structure "FieldDescType", but fieldDA
* member (a LONG) is at a 32-bit alignment already (at byte offset +12).
* The altFieldLength member, same structure, is also already at
* proper alignment for a 16-bit value (at byte offset +18). If, for
* some reason, your compiler aligns the members differently, then you
* must use the appropriate compiler pragma to prevent this -- the
* FieldDescType size is 32 bytes exactly. It is not likely that any
* conforming compiler will alter this structure, but, now you know what
* to do if it does.
*
* #pragma pack(1)
*
* NOTE: In your program source code, when you layout your record buffer
* structure, you must use the #pragma pack(1)/#pragma pack() directives
* around it since it will be, most likely, modified. The reason is that
* this structure MUST start with the implicit TAG field (a BYTE), and so,
* unless you use only BYTE/CHAR members in your structure (Bullet can use
* binary field values), or take special care to align the record layout
* so no padding is performed by the compiler, then you will need to use
* the pack(1) pragma.
*
* #pragma pack()
*/
/* Re: Bullet/X for DOSX32:
* (refer to ccdosfn.c for more)
* Bullet ccdosfn.c provides no OS call support to determine the
* system country code and code page ID. This can be coded in
* ccdosfn.c, but it is quite compiler- and extender-dependent.
* The current state is to supply a collate sequence table for
* country code=1 and code page=437 (the table is statically coded).
* For other sort tables, modify as required. If support for this
* at the system level is made available at run-time, you may
* want to change the following to 0, for both CTRYCODE and CODEPAGE.
* Until this is so coded, you cannot use 0 here (as with the Win32
* and OS/2 versions), else error EXB_216501 (8251) is the result.
* This table is added to each index file (if NLS or a user sort).
*/
#ifndef PLATFORM
#error No PLATFORM specified
#error ---------------------
#endif
#ifndef ON_DOSX32
#define ON_DOSX32 3
#define ON_OS2 4
#define ON_WIN32 5
#endif
#if PLATFORM == ON_DOSX32
#define CTRYCODE 1 /* 0 signifies default country code (at index create) */
#define CODEPAGE 437 /* 0 signifies default code page (at index create) */
/* but DOS extender may not support OS call to get info */
/* see ccdosfn.c for making changes for DOSX32 platform */
#define RELOCK_AVAIL 0 /* relock not supported */
#define VOID void /* these are already defined if Win32 or OS/2, but not DOS */
#define SHORT short
#define LONG long
#define CHAR char
typedef unsigned char BYTE;
typedef unsigned short USHORT;
typedef unsigned long ULONG;
typedef unsigned char *PSZ;
typedef VOID *PVOID;
#define APIENTRY __cdecl
#elif PLATFORM == ON_OS2
#define CTRYCODE 0
#define CODEPAGE 0
#define RELOCK_AVAIL 1 /* relock is supported */
/* above types are assumed defined in os2def.h */
#elif PLATFORM == ON_WIN32
#define CTRYCODE 0
#define CODEPAGE 0 /* may be ANSI or OEM code page value, depending on USE*CHARSET flag */
#define RELOCK_AVAIL 0 /* relock not supported */
/* above types are assume defined in windef.h and winnt.h */
#else
#error No PLATFORM specified
#error ---------------------
#endif
#ifndef __BLT_DYNA // define this if using run-time loading of BULLET*.DLL
// via LoadLibrary(Win32) or DosLoadModule(OS/2)
#ifdef __cplusplus
extern "C" LONG APIENTRY BULLET(PVOID datapack);
#else
extern LONG APIENTRY BULLET(PVOID datapack);
#endif
#else
LONG (* APIENTRY BULLET)(PVOID datapack);
#endif
/* The following on mutex-semaphore protection does not apply to Bullet/X */
/* unless the semaphore routines (in ccdosfn.c) are coded to do something */
/* All Bullet routines are mutex-semaphore protected except the following:
*
* MEMORY_XB STAT_HANDLE_XB GET_ERROR_CLASS_XB
* QUERY_SYSVARS_XB QUERY_VECTORS_XB CHECK_REMOTE_XB
* STAT_DATA_XB STAT_INDEX_XB
*
* This means that any thread can call the above routines at any time. All
* other calls in the current process block until the previous thread exits
* BULLET. The default mutex wait is 0 milliseconds, and can be set via
* SET_SYSVARS_XB using the MUTEX_SEM_TIMEOUT index. In the case of
* STAT_DATA_XB and STAT_INDEX_XB, these should be used only when there
* is no chance that another thread may close that file handle while the
* routine is working.
*
*/
/* ************************************************************************
*
* xxx.func call numbers
*
* ************************************************************************/
#define GEN_ERR_XB 0
#define INIT_XB 1 /* system */
#define EXIT_XB 2
#define MEMORY_XB 4
#define BACKUP_FILE_XB 6
#define STAT_HANDLE_XB 7
#define GET_ERROR_CLASS_XB 8
#define QUERY_SYSVARS_XB 10 /* advanced system */
#define SET_SYSVARS_XB 11
#define SET_DVMON_XB 12 /* reserved */
#define QUERY_VECTORS_XB 13
#define SET_VECTORS_XB 14
#define CREATE_DATA_XB 20 /* data control mid-level */
#define OPEN_DATA_XB 21
#define CLOSE_DATA_XB 22
#define STAT_DATA_XB 23
#define READ_DATA_HEADER_XB 24
#define FLUSH_DATA_HEADER_XB 25
#define COPY_DATA_HEADER_XB 26
#define ZAP_DATA_HEADER_XB 27
#define CREATE_INDEX_XB 30 /* key control mid-level */
#define OPEN_INDEX_XB 31
#define CLOSE_INDEX_XB 32
#define STAT_INDEX_XB 33
#define READ_INDEX_HEADER_XB 34
#define FLUSH_INDEX_HEADER_XB 35
#define COPY_INDEX_HEADER_XB 36
#define ZAP_INDEX_HEADER_XB 37
#define GET_DESCRIPTOR_XB 40 /* data access mid-level */
#define GET_RECORD_XB 41
#define ADD_RECORD_XB 42
#define UPDATE_RECORD_XB 43
#define DELETE_RECORD_XB 44
#define UNDELETE_RECORD_XB 45
#define DEBUMP_RECORD_XB 46
#define PACK_RECORDS_XB 47
#define GET_MEMO_SIZE_XB 50 /* memo access mid-level */
#define GET_MEMO_XB 51
#define ADD_MEMO_XB 52
#define UPDATE_MEMO_XB 53
#define DELETE_MEMO_XB 54
#define MEMO_BYPASS_XB 59 /* see below for bypass ordinals */
#define BYPASS_CREATE_MEMO 1 /* The bypass routines are automatically */
#define BYPASS_OPEN_MEMO 2 /* performed by BULLET but can be done */
#define BYPASS_CLOSE_MEMO 3 /* manually, if needed - these numbers are */
#define BYPASS_READ_MEMO_HEADER 4 /* put in MDP.memoBypass, with MDP.func */
#define BYPASS_FLUSH_MEMO_HEADER 5 /* set to MEMO_BYPASS_XB */
#define FIRST_KEY_XB 60 /* key access mid-level */
#define EQUAL_KEY_XB 61
#define EQUAL_OR_GREATER_KEY_XB 110
#define EQUAL_OR_LESSER_KEY_XB 111
#define NEXT_KEY_XB 62
#define PREV_KEY_XB 63
#define LAST_KEY_XB 64
#define STORE_KEY_XB 65
#define DELETE_KEY_XB 66
#define BUILD_KEY_XB 67
#define GET_CURRENT_KEY_XB 68
#define GET_KEY_FOR_RECORD_XB 69
#define GET_FIRST_XB 70 /* key and data access high-level */
#define GET_EQUAL_XB 71
#define GET_EQUAL_OR_GREATER_XB 112
#define GET_EQUAL_OR_LESSER_XB 113
#define GET_NEXT_XB 72
#define GET_PREV_XB 73
#define GET_LAST_XB 74
#define INSERT_XB 75
#define UPDATE_XB 76
#define REINDEX_XB 77
#define LOCK_XB 80 /* network control */
#define UNLOCK_XB 81
#define LOCK_INDEX_XB 82
#define UNLOCK_INDEX_XB 83
#define LOCK_DATA_XB 84
#define UNLOCK_DATA_XB 85
#define CHECK_REMOTE_XB 86
#define RELOCK_XB 87
#define RELOCK_INDEX_XB 88
#define RELOCK_DATA_XB 89
#define DELETE_FILE_DOS 90 /* DOS file I/O low-level */
#define RENAME_FILE_DOS 91
#define CREATE_FILE_DOS 92
#define OPEN_FILE_DOS 93
#define SEEK_FILE_DOS 94
#define READ_FILE_DOS 95
#define WRITE_FILE_DOS 96
#define CLOSE_FILE_DOS 97
#define ACCESS_FILE_DOS 98
#define EXPAND_FILE_DOS 99
#define MAKE_DIR_DOS 100
#define COMMIT_FILE_DOS 101
/* ************************************************************************
*
* operating system file I/O equates
*
* ************************************************************************/
#define READONLY 0x00000000 /* std file access mode */
#define WRITEONLY 0x00000001 /* no underscore used for std equates */
#define READWRITE 0x00000002
#define DENYREADWRITE 0x00000010 /* std file share mode, cannot be 0 */
#define DENYWRITE 0x00000020
#define DENYREAD 0x00000030
#define DENYNONE 0x00000040
#define NOINHERIT 0x00000080
#define NO_LOCALITY 0x00000000 /* optional cache modes */
#define SEQ_LOCALITY 0x00010000
#define RND_LOCALITY 0x00020000
#define MIX_LOCALITY 0x00030000
#define SKIP_CACHE 0x00100000 /* not inherited by child process */
#define WRITE_THROUGH 0x00400000 /* not inherited by child process */
#define LOCK_SHARED 1 /* a read-only lock, OS/2 and NT only */
#define LOCK_EXCLUSIVE 0 /* default */
/* ************************************************************************
*
* .sortFunction IDs, Query/SetSysVars|Vectors item IDs
*
* ************************************************************************/
// SORT_SET flag: USE_*_CHARSET defaults to OEM character set, and is used
// (OEM or ANSI) if the .sortFunction is NLS or a custom sort-compare.
#define USE_OEM_CHARSET (0 << 17) /* for DOSX32, OS/2, and Windows */
#define USE_ANSI_CHARSET (1 << 17) /* for Windows (.sortFunction flag) */
#define DUPS_ALLOWED (1 << 16) /* allow duplicate keys (.sortFunction flag) */
/* All Bullet system vars set to default values at INIT_XB */
/* Sorts 1-19 also used as CIP.sortFunction (can be OR'ed with DUPS_ALLOWED) */
/* Intrinsic sorts (1-6) are read-only (R-O) */
#define ASCII_SORT 1 /* sort by: ASCII value (R-O) */
#define NLS_SORT 2 /* NLS (R-O) */
#define S16_SORT 3 /* 16-bit signed integer (R-O) */
#define U16_SORT 4 /* 16-bit unsigned integer (R-O) */
#define S32_SORT 5 /* 32-bit signed integer (R-O) */
#define U32_SORT 6 /* 32-bit unsigned integer (R-O) */
/* sorts 7 to 9 are reserved */
/* Custom sort-compare functions are from 10 to 19 */
#define BUILD_KEY_FUNC 20 /* key build function ptr */
#define PARSER_FUNC 21 /* key expression parser function ptr */
#define MUTEX_SEM_HANDLE 29 /* handle of Bullet's mutex semaphore (R-O) */
#define LOCK_TIMEOUT 30 /* lock-wait timeout (default=0, no wait)*/
#define MUTEX_SEM_TIMEOUT 31 /* mutex semaphore-wait timeout (def=0,none) */
#define PACK_BUFFER_SIZE 32 /* pack buffer size (def=0, min autosize) */
#define REINDEX_BUFFER_SIZE 33 /* reindex buffer size (def=0, min autosize) */
#define REINDEX_PACK_PCT 34 /* reindex node pack % (default=100, max) */
#define TMP_PATH_PTR 35 /* temporary file path ptr (default=NULL) */
#define REINDEX_SKIP_TAG 36 /* index skip tag select (default=0, none) */
#define COMMIT_AT_EACH 37 /* commit each insert/update in pack (def=0) */
#define MEMO_BLOCKSIZE 38 /* memo block size (default=512 bytes) */
#define MEMO_EXTENSION 39 /* memo filename extension (default='DBT\0') */
#define MAX_DATAFILE_SIZE 40 /* max data size (default=0x7FEFFFFF=2095MB) */
#define MAX_INDEXFILE_SIZE 41 /* max index size (default=0x7FEFFFFF=2095MB)*/
#define ATOMIC_MODE 42 /* bit0=1 atomic next/prev key access (def=0)*/
#define CALLBACK_PTR 43 /* callback at reindex/pack (def=0, none) */
/* ************************************************************************
*
* Query/SetVectors vector IDs
*
* ************************************************************************/
#define VECTOR_CLOSE_FILE 2
#define VECTOR_CREATE_DIR 3
#define VECTOR_CREATE_FILE 4
#define VECTOR_CREATE_UNIQUE_FILE 5
#define VECTOR_DELETE_FILE 6
#define VECTOR_LENGTH_FILE 7
#define VECTOR_MOVE_FILE 8
#define VECTOR_OPEN_FILE 9
#define VECTOR_READ_FILE 10
#define VECTOR_SEEK_FILE 11
#define VECTOR_UPDATE_DIR_ENTRY 12
#define VECTOR_WRITE_FILE 13
#define VECTOR_LOCK_FILE 14
#define VECTOR_IS_DRIVE_REMOTE 15
#define VECTOR_IS_FILE_REMOTE 16
#define VECTOR_EXITLIST 17
#define VECTOR_REMOVE_EXITLIST 18
#define VECTOR_FREE 19
#define VECTOR_GET_SORT_TABLE 20
#define VECTOR_GET_COUNTRY_INFO 21
#define VECTOR_GET_ERROR_CLASS 22
#define VECTOR_GET_MEMORY 23
#define VECTOR_GET_TMP_DIR 24
#define VECTOR_GET_VERSION 25
#define VECTOR_MALLOC 26
#define VECTOR_SET_HANDLE_COUNT 27
#define VECTOR_GET_TIME_INFO 28
#define VECTOR_UPPERCASE 29
#define VECTOR_CLOSE_MUTEX_SEM 30
#define VECTOR_CREATE_MUTEX_SEM 31
#define VECTOR_RELEASE_MUTEX_SEM 32
#define VECTOR_REQUEST_MUTEX_SEM 33
/* ************************************************************************
*
* Parameter pack structures, typedefs
*
* ************************************************************************/
/* AP, CP, CDP, etc., are suggested variable names */
typedef struct _ACCESSPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to access */
LONG recNo; /* IO, record number */
PVOID recPtr; /* I, programmer's record buffer */
PVOID keyPtr; /* I, programmer's key buffer */
PVOID nextPtr; /* I, NULL if not xaction, else next AP in list */
} ACCESSPACK; /* AP */
typedef ACCESSPACK *PACCESSPACK;
/* CBP is the structure received by the callback procedure */
/* structure members are filled in by Bullet */
typedef struct _CALLBACKPACK {
ULONG sizeIs; /* structure size (current 16 bytes) */
ULONG callMode; /* 0=from reindex; 1=from DBF pack */
ULONG handle; /* file handle */
ULONG data1; /* for callMode=0/1: progress percent (1-99,0) */
} CALLBACKPACK; /* CBP */
typedef CALLBACKPACK *PCALLBACKPACK;
typedef struct _COPYPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to copy */
PSZ filenamePtr; /* I, filename to use (drv+path must exist if used) */
} COPYPACK; /* CP */
typedef COPYPACK *PCOPYPACK;
typedef struct _CREATEDATAPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
ULONG noFields; /* I, 1 to 1024 */
PVOID fieldListPtr; /* I, descriptor list, 1 per field */
ULONG fileID; /* I, 0x03 for standard DBF, 0x8B if memo file also */
} CREATEDATAPACK; /* CDP */
typedef CREATEDATAPACK *PCREATEDATAPACK;
typedef struct _CREATEINDEXPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
PSZ keyExpPtr; /* I, e.g., "SUBSTR(LNAME,1,4)+SSN" */
LONG xbLink; /* I, opened data file handle this indexes */
ULONG sortFunction; /* I, 1-9 system, 10-19 custom */
ULONG codePage; /* I, 0=use process default */
ULONG countryCode; /* I, 0=use process default */
PVOID collatePtr; /* I, NULL=use cc/cp else use passed table for sort */
ULONG nodeSize; /* I, 512, 1024, or 2048 */
} CREATEINDEXPACK; /* CIP */
typedef CREATEINDEXPACK *PCREATEINDEXPACK;
typedef struct _FIELDDESCTYPE {
BYTE fieldName[11]; /* IO, upper A-Z and _; 1-10 chars, 0-filled, 0-term */
BYTE fieldType; /* IO, C,D,L,N, or M */
LONG fieldDA; /* x, offset within record (run-time storage option) */
BYTE fieldLen; /* IO, C=1-255,D=8,L=1,N=1-19,M=10 */
BYTE fieldDC; /* IO, fieldType=N then 0-15 else 0 */
USHORT altFieldLength;/* IO, 0 */
BYTE filler[12]; /* I, 0 */
} FIELDDESCTYPE; /* nested in _DESCRIPTORPACK */
typedef FIELDDESCTYPE *PFIELDDESCTYPE;
typedef struct _DESCRIPTORPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of DBF file */
ULONG fieldNumber; /* IO, first field is 1 */
ULONG fieldOffset; /* O, offset of field within record (tag=offset 0) */
FIELDDESCTYPE FD; /* IO FD.fieldName only, O for rest of FD */
} DESCRIPTORPACK; /* DP */
typedef DESCRIPTORPACK *PDESCRIPTORPACK;
typedef struct _DOSFILEPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
ULONG handle; /* IO, handle of open file */
ULONG asMode; /* I, access-sharing mode */
ULONG bytes; /* IO, bytes to read, write, length of */
LONG seekTo; /* IO, seek to offset, current offset */
ULONG method; /* I, seek method (0=start of file, 1=current, 2=end) */
PVOID bufferPtr; /* I, buffer to read into or write from */
ULONG attr; /* I, attribute to create file with */
PSZ newNamePtr; /* I, name to use on rename */
} DOSFILEPACK; /* DFP */
typedef DOSFILEPACK *PDOSFILEPACK;
typedef struct _EXITPACK {
ULONG func;
ULONG stat;
} EXITPACK; /* EP */
typedef EXITPACK *PEXITPACK;
typedef struct _HANDLEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file */
} HANDLEPACK; /* HP */
typedef HANDLEPACK *PHANDLEPACK;
typedef struct _INITPACK {
ULONG func;
ULONG stat;
ULONG JFTsize; /* I, max opened files (20-1024+) */
ULONG versionDOS; /* O, e.g., 230 for 2.30 */
ULONG versionBullet; /* O, e.g., 2019 for 2.019 */
ULONG versionOS; /* O, e.g., 4=OS/2 32-bit */
PVOID exitPtr; /* O, function pointer to EXIT_XB routine */
} INITPACK; /* IP */
typedef INITPACK *PINITPACK;
typedef struct _LOCKPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to lock */
ULONG xlMode; /* I, index lock mode (0=exclusive, 1=shared) */
ULONG dlMode; /* I, data lock mode (0=exclusive, 1=shared) */
LONG recStart; /* I, if data, first record # to lock, or 0 for all */
ULONG recCount; /* I, if data and recStart!=0, # records to lock */
PVOID nextPtr; /* I, NULL if not xaction, else next LP in list */
} LOCKPACK; /* LP */
typedef LOCKPACK *PLOCKPACK;
typedef struct _MEMODATAPACK {
ULONG func;
ULONG stat;
ULONG dbfHandle; /* I, handle of DBF file to which this memo file belongs */
ULONG memoBypass; /* I, memo bypass function to do, if any */
PVOID memoPtr; /* I, ptr to memo record buffer */
ULONG memoNo; /* IO, memo record number (aka block number) */
ULONG memoOffset; /* I, position within record to start read/update */
ULONG memoBytes; /* IO, number of bytes to read/update */
} MEMODATAPACK; /* MDP */
typedef MEMODATAPACK *PMEMODATAPACK;
typedef struct _MEMORYPACK {
ULONG func;
ULONG stat;
ULONG memory; /* O, not used in OS/2 */
} MEMORYPACK; /* MP */
typedef MEMORYPACK *PMEMORYPACK;
typedef struct _OPENPACK {
ULONG func;
ULONG stat;
ULONG handle; /* O, handle of file opened */
PSZ filenamePtr; /* I, Bullet file to open */
ULONG asMode; /* I, access-sharing-cache mode */
LONG xbLink; /* I, if index open, xbLink=handle of its opened DBF */
} OPENPACK; /* OP */
typedef OPENPACK *POPENPACK;
typedef struct _QUERYSETPACK {
ULONG func;
ULONG stat;
ULONG item; /* I, Bullet sysvar item to get/set */
ULONG itemValue; /* IO, current/new value */
} QUERYSETPACK; /* QSP */
typedef QUERYSETPACK *PQUERYSETPACK;
typedef struct _REMOTEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of file, or if 0, use RP.drive */
ULONG drive; /* I, drive (1=A,2=B,3=C,...0=current) to check */
ULONG isRemote; /* O, =1 of handle/drive is remote, =0 if local */
ULONG flags; /* O, 0 */
ULONG isShare; /* O, 1 */
} REMOTEPACK; /* RP */
typedef REMOTEPACK *PREMOTEPACK;
typedef struct _STATDATAPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
ULONG fileType; /* O, bit0=1 data file */
ULONG flags; /* O, bit0=1 dirty, bit1=1 full-lock, bit2=1 shared */
ULONG progress; /* O, 0,1-99% pack progress */
PVOID morePtr; /* O, 0 */
ULONG fields; /* O, fields per record */
ULONG asMode; /* O, access-sharing-cache mode */
PSZ filenamePtr; /* O, filename used in open */
ULONG fileID; /* O, first byte of DBF file */
ULONG lastUpdate; /* O, high word=year,low byte=day, high byte=month */
ULONG records; /* O, data records (including "deleted") */
ULONG recordLength; /* O, record length */
ULONG xactionFlag; /* O, 0 */
ULONG encryptFlag; /* O, 0 */
PVOID herePtr; /* O, this file's control address */
ULONG memoHandle; /* O, handle of open memo file (0 if none) */
ULONG memoBlockSize; /* O, memo file block size */
ULONG memoFlags; /* O, bit0=1 dirty */
ULONG memoLastRecord; /* O, last accessed memo record (0 if none) */
ULONG memoLastSize; /* O, size of last accessed memo record (in bytes, +8) */
ULONG lockCount; /* O, number of full-locks in force */
} STATDATAPACK; /* SDP */
typedef STATDATAPACK *PSTATDATAPACK;
typedef struct _STATHANDLEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
LONG ID; /* O, bit0=1 data file, bit0=1 index file */
} STATHANDLEPACK; /* SHP */
typedef STATHANDLEPACK *PSTATHANDLEPACK;
typedef struct _STATINDEXPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
ULONG fileType; /* O, bit0=0 index file */
ULONG flags; /* O, bit0=1 dirty, bit1=1 full-lock, bit2=1 shared */
ULONG progress; /* O, 0,1-99% reindex progress */
PVOID morePtr; /* O, 0 */
ULONG xbLink; /* O, XB file link handle */
ULONG asMode; /* O, access-sharing-cache mode */
PSZ filenamePtr; /* O, pointer to filename used in open */
ULONG fileID; /* O, "31ch" */
PSZ keyExpPtr; /* O, pointer to key expression */
ULONG keys; /* O, keys in file */
ULONG keyLength; /* O, key length */
ULONG keyRecNo; /* O, record number of current key */
PVOID keyPtr; /* O, ptr to current key value (valid to keyLength) */
PVOID herePtr; /* O, this file's control address */
ULONG codePage; /* O, code page at create time */
ULONG countryCode; /* O, country code at create time */
PVOID CTptr; /* O, collate table ptr, NULL=no collate table present */
ULONG nodeSize; /* O, node size */
ULONG sortFunction; /* O, sort function ID */
ULONG lockCount; /* O, number of full-locks in force */
} STATINDEXPACK; /* SIP */
typedef STATINDEXPACK *PSTATINDEXPACK;
typedef struct _XERRORPACK {
ULONG func;
ULONG stat; /* I, error to check */
ULONG errClass; /* O, class of error */
ULONG action; /* O, action recommended for error */
ULONG location; /* O, location of error */
} XERRORPACK; /* XEP */
typedef XERRORPACK *PXERRORPACK;
/* ************************************************************************
*
* Error codes
*
* ************************************************************************/
#define EXB_NOT_ENOUGH_MEMORY 8 /* cannot get memory requested */
#define EXB_INVALID_DRIVE 15 /* not a valid drive letter */
#define EXB_UNEXPECTED_EOF 38 /* unexpect EOF (bytes read != bytes asked) */
#define EXB_DISK_FULL 39 /* disk full on WriteFile */
#define EXB_FILE_EXISTS 80 /* cannot create file since it already exists */
#define EXB_SEM_OWNER_DIED 105 /* in place of Win32 error 80h (mutex) */
#define EXB_TIMEOUT 640 /* in place of Win32 error 102h (mutex) */
/* Other operating system errors are as returned by OS itself */
/* System/general error codes */
#define EXB_OR_WITH_FAULTS 8192 /* 8192+1 to +4, close-type errors */
/* ERR_216501/6 are for Bullet/x only */
#define EXB_216501 8251 /* INT21/6501h not supported by DOS extender */
/* (do not use default cc/cp) */
#define EXB_216506 8256 /* INT21/6506h not supported by DOS extender */
/* (provide a custom collate table) */
#define EXB_ILLEGAL_CMD 8300 /* function not allowed */
#define EXB_OLD_DOS 8301 /* OS version < MIN_DOS_NEEDED */
#define EXB_NOT_INITIALIZED 8302 /* init not active, must do INIT_XB */
#define EXB_ALREADY_INITIALIZED 8303 /* init already active, must do EXIT_XB */
#define EXB_TOO_MANY_HANDLES 8304 /* more than 1024 opens requested */
#define EXB_SYSTEM_HANDLE 8305 /* Bullet won't use or close handles 0-2 */
#define EXB_FILE_NOT_OPEN 8306 /* file not open (not Bullet handle, including xbLink) */
#define EXB_FILE_IS_DIRTY 8307 /* tried to reload header but current still dirty */
#define EXB_BAD_FILETYPE 8308 /* tried key op on non-key file, data op on non... */
#define EXB_TOO_MANY_PACKS 8309 /* too many INSERT,UPDATE,REINDEX,LOCK_XB packs */
#define EXB_NULL_RECPTR 8310 /* null record pointer passed to Bullet */
#define EXB_NULL_KEYPTR 8311 /* null key pointer passed to Bullet */
#define EXB_NULL_MEMOPTR 8312 /* null memo pointer passed to Bullet */
#define EXB_EXPIRED 8313 /* evaluation time period has expired */
#define EXB_BAD_INDEX 8314 /* Query/SetSysVars index beyond last one */
#define EXB_RO_INDEX 8315 /* SetSysVar index item is read-only */
#define EXB_FILE_BOUNDS 8316 /* file size > 4GB, or > system var sets */
#define EXB_FORCE_ROLLBACK 8397 /* rollback test completed (internal use) */
#define EXB_INVALID_DLL 8398 /* DLL seems to be invalid, 8399 same */
/* Multi-access error codes */
#define EXB_BAD_LOCK_MODE 8401 /* lock mode (LP) not valid */
#define EXB_NOTHING_TO_RELOCK 8402 /* cannot relock without existing full-lock */
#define EXB_SHARED_LOCK_ON 8403 /* write access needed but lock is shared (flush on backup) */
/* Index error codes */
#define EXB_KEY_NOT_FOUND 8501 /* exact match of key not found */
#define EXB_KEY_EXISTS 8502 /* key exists already and dups not allowed */
#define EXB_END_OF_FILE 8503 /* already at last index order */
#define EXB_TOP_OF_FILE 8504 /* already at first index order */
#define EXB_EMPTY_FILE 8505 /* nothing to do since no keys */
#define EXB_CANNOT_GET_LAST 8506 /* cannot locate last key */
#define EXB_BAD_INDEX_STACK 8507 /* index file is corrupt */
#define EXB_BAD_INDEX_READ0 8508 /* index file is corrupt */
#define EXB_BAD_INDEX_WRITE0 8509 /* index file is corrupt */
#define EXB_OLD_INDEX 8521 /* old index, run through ReindexOld to update */
#define EXB_UNKNOWN_INDEX 8522 /* not a Bullet index file */
#define EXB_KEY_TOO_LONG 8523 /* keylength > 62 (or 64 if unique), or is 0 */
#define EXB_PARSER_NULL 8531 /* parser function pointer is NULL */
#define EXB_BUILDER_NULL 8532 /* build key function pointer is NULL */
#define EXB_BAD_SORT_FUNC 8533 /* CIP.sortFunction not valid */
#define EXB_BAD_NODE_SIZE 8534 /* CIP.nodeSize is not 512, 1024, or 2048 */
#define EXB_FILENAME_TOO_LONG 8535 /* CIP.filenamePtr->pathname > max path length */
#define EXB_KEYX_NULL 8541 /* expression is effectively NULL */
#define EXB_KEYX_TOO_LONG 8542 /* CIP.keyExpPtr->expression > 159 */
#define EXB_KEYX_SYM_TOO_LONG 8543 /* fieldname/funcname in expression > 10 chars */
#define EXB_KEYX_SYM_UNKNOWN 8544 /* fieldname/funcname in expression unknown */
#define EXB_KEYX_TOO_MANY_SYMS 8545 /* too many symbols/fields used in expression */
#define EXB_KEYX_BAD_SUBSTR 8546 /* invalid SUBSTR() operand in expression */
#define EXB_KEYX_BAD_SUBSTR_SZ 8547 /* SUBSTR() exceeds field's size */
#define EXB_KEYX_BAD_FORM 8548 /* didn't match expected symbol in expression */
#define EXB_NO_READS_FOR_RUN 8551 /* unlikely, use different reindex buffer size */
#define EXB_TOO_MANY_RUNS 8552 /* unlikely, too many runs (64K or more runs) */
#define EXB_TOO_MANY_RUNS_FOR_BUFFER 8553 /* unlikely, too many runs for run buffer */
#define EXB_TOO_MANY_DUPLICATES 8554 /* more than 64K "identical" keys */
#define EXB_INSERT_RECNO_BAD 8561 /* AP.recNo cannot be > 0 if inserting */
#define EXB_PREV_APPEND_EMPTY 8562 /* no prev append for insert yet AP.recNo==80000000h */
#define EXB_PREV_APPEND_MISMATCH 8563 /* prev append's xbLink does not match this */
#define EXB_INSERT_KBO_FAILED 8564 /* could not back out key at INSERT_XB */
#define EXB_INSERT_DBO_FAILED 8565 /* could not back out data records at INSERT_XB */
#define WRN_NOTHING_TO_UPDATE 8571 /* all AP.recNo=0 at UPDATE_XB */
#define EXB_INTERNAL_UPDATE 8572 /* internal error UPDATE_XB, not in hdl/rec# list */
#define EXB_FAILED_DATA_RESTORE 8573 /* could not restore original data record (*) */
#define EXB_FAILED_KEY_DELETE 8574 /* could not remove new key (*) */
#define EXB_FAILED_KEY_RESTORE 8575 /* could not restore original key(*) */
/* *original error, which forced a back-out, has been replaced by this error */
/* this error is always returned in the first AP.stat (-1 on data, 1 on index) */
/* Data error codes */
#define EXB_EXT_XBLINK 8601 /* xbLink handle is not internal DBF (is -1) */
#define EXB_FIELDNAME_TOO_LONG 8602 /* fieldname is > 10 characters */
#define EXB_RECORD_TOO_LONG 8603 /* record length is > 64K */
#define EXB_FIELD_NOT_FOUND 8604 /* fieldname not found in descriptor info */
#define EXB_BAD_FIELD_COUNT 8605 /* fields <= 0 or >= MAX_FIELDS (Init,Open) */
/* and also GetDescriptor by field number */
#define EXB_BAD_HEADER 8606 /* bad header (reclen=0, etc., from LocateTo, Flush) */
#define EXB_BUFFER_TOO_SMALL 8607 /* buffer too small (pack buffer < reclen in pack) */
#define EXB_INTERNAL_PACK 8608 /* internal error in PackRecords */
#define EXB_BAD_RECNO 8609 /* record number=0 or > records in data file hdr */
/* or Pack on empty data file */
#define WRN_RECORD_TAGGED 8610 /* record's tag field matches skip tag */
/* Memo error codes */
#define WRN_CANNOT_OPEN_MEMO 8701 /* DBF says memo file but memo open fails */
#define EXB_MEMO_NOT_OPEN 8702 /* no open memo file for operation */
#define EXB_BAD_BLOCKSIZE 8703 /* memo blocksize must be at least 24 bytes */
#define EXB_MEMO_DELETED 8704 /* memo is deleted */
#define EXB_MEMO_PAST_END 8705 /* memo data requested is past end of record */
#define EXB_BAD_MEMONO 8706 /* memo number is not valid */
#define EXB_MEMO_IN_USE 8707 /* memo add encountered likely corrupt memo file */
#define EXB_BAD_AVAIL_LINK 8708 /* memo avail link cannot be valid (is 0) */
#define EXB_MEMO_ZERO_SIZE 8709 /* memo data has no size */
#define EXB_MEMO_IS_SMALLER 8710 /* memo attempt to shrink but already <= size */
#endif /* ifndef __BULLET_H */
ΓòÉΓòÉΓòÉ 9. Bullet Functions ΓòÉΓòÉΓòÉ
System level Advanced system-level
INIT_XB QUERY_SYSVARS_XB
EXIT_XB SET_SYSVARS_XB
MEMORY_XB SET_DVMON_XB
BACKUP_FILE_XB QUERY_VECTORS_XB
STAT_HANDLE_XB SET_VECTORS_XB
GET_ERROR_CLASS_XB
Data low-level Data mid-level Memo
mid-level
CREATE_DATA_XB GET_DESCRIPTOR_XB
GET_MEMO_SIZE_XB
OPEN_DATA_XB GET_RECORD_XB GET_MEMO_XB
CLOSE_DATA_XB ADD_RECORD_XB ADD_MEMO_XB
STAT_DATA_XB UPDATE_RECORD_XB
UPDATE_MEMO_XB
READ_DATA_HEADER_XB DELETE_RECORD_XB
DELETE_MEMO_XB
FLUSH_DATA_HEADER_XB UNDELETE_RECORD_XB
MEMO_BYPASS_XB
COPY_DATA_HEADER_XB DEBUMP_RECORD_XB
ZAP_DATA_HEADER_XB PACK_RECORDS_XB
Index low-level Index mid-level Index
high-level
CREATE_INDEX_XB FIRST_KEY_XB GET_FIRST_XB
OPEN_INDEX_XB EQUAL_KEY_XB GET_EQUAL_XB
CLOSE_INDEX EQUAL_OR_GREATER_KEY_XB
GET_EQUAL_OR_GREATER_XB
STAT_INDEX_XB EQUAL_OR_LESSER_KEY_XB
GET_EQUAL_OR_LESSER_XB
READ_INDEX_HEADER_XB NEXT_KEY_XB GET_NEXT_XB
FLUSH_INDEX_HEADER_XB PREV_KEY_XB GET_PREV_XB
COPY_INDEX_HEADER_XB LAST_KEY_XB GET_LAST_XB
ZAP_INDEX_HEADER_XB STORE_KEY_XB INSERT_XB
DELETE_KEY_XB UPDATE_XB
BUILD_KEY_XB REINDEX_XB
GET_CURRENT_KEY_XB
GET_KEY_FOR_RECORD_XB
Network level CP level
LOCK_XB DELETE_FILE_DOS
UNLOCK_XB RENAME_FILE_DOS
LOCK_INDEX_XB CREATE_FILE_DOS
UNLOCK_INDEX_XB OPEN_FILE_DOS
LOCK_DATA_XB SEEK_FILE_DOS
UNLOCK_DATA_XB READ_FILE_DOS
CHECK_REMOTE_XB WRITE_FILE_DOS
RELOCK_XB CLOSE_FILE_DOS
RELOCK_INDEX_XB ACCESS_FILE_DOS
RELOCK_DATA_XB EXPAND_FILE_DOS
MAKE_DIR_DOS
COMMIT_FILE_DOS
ΓòÉΓòÉΓòÉ 9.1. INIT_XB ΓòÉΓòÉΓòÉ
Pack: INITPACK Source Example
IN OUT
IP.func IP.stat
IP.JFTsize IP.versionDOS
IP.versionBullet
IP.versionOS
IP.exitPtr
This must be the first routine called (except for SET_VECTORS_XB). If it has
already been called the system variables are restored to their defaults, and
an error is returned. Otherwise, the entire Bullet system is initialized, and
EXIT_XB is registered with the OS ExitList handler (DosExitList).
For more than the default open files (generally 20), set IP.JFTsize to the
total number of concurrently open files you need. Depending on your version,
Bullet manages up to 1024 Bullet files per process (total data and index; memo
files are not counted against this total). Setting this less than 20 does
nothing. This number is for Bullet files, your files, pipes -- anything using
a handle. If you need to account for handles that you are managing, you
should add those to IP.JFTsize. For example, if you need 10 data files, each
with a memo file, and 2 index files per data file, that is 40 total Bullet
files. If you need to use 15 other handles, for whatever use, add that number
to the 40 Bullet files, for a total setting of 55. The OS also uses 3 handles
for itself, so, for all these, IP.JFTsize=58 would be the minimum. You can
set it higher, but unused handles are wasted handles. In addition, if the
current process has fewer total handles available than the number you
specified in IP.JFTsize, Bullet sets the total available handles to IP.JFTsize
(as the absolute number of handles required). If the current process already
has more total handles than IP.JFTsize, no action is taken.
On return (where no error occurred), the operating system version is in
IP.versionDOS (*100) and the Bullet version (*1000) in IP.versionBullet.
IP.versionOS return is based on the following table:
Bullet Platform IP.versionOS
MS-DOS 16-bit 0
Win3x 16-bit 1
DOSX32 32-bit 3
OS/2 32-bit 4
Win32 32-bit 5
IP.exitPtr returns with the function pointer to the EXIT_XB routine. This
function pointer is redundant unless specifically mentioned as being required
for your platform. It is not needed in OS/2, but is useful for other
platforms, for example, for use with atexit(), part of C's standard library.
Win32s, as run in Windows 3.1, returns IP.versionDOS equal to its level. For
example, if running Win31, using win32s version 1.25, IP.versionDOS returns as
125, not 310 as might be expected.
Note: References under OUT using *AP.keyPtr or similar (note then *) are used
throughout this manual and indicate that Bullet updates the contents at
AP.keyPtr with data (i.e., Bullet filled the buffer).
ΓòÉΓòÉΓòÉ 9.2. EXIT_XB ΓòÉΓòÉΓòÉ
Pack: EXITPACK Source Example
IN OUT
EP.func EP.stat
Call EXIT_XB before ending your program to release any remaining resources
back to the OS. Open files should be closed by using CLOSE_DATA_XB and
CLOSE_INDEX_XB. EXIT_XB closes any Bullet files that are still open.
This routine is registered with the operating system and so is called
automatically when your program terminates in OS/2, or with the C startup code
if atexit() is used.
ΓòÉΓòÉΓòÉ 9.3. ATEXIT_XB ΓòÉΓòÉΓòÉ
This routine is obsolete. In OS/2, the EXIT_XB shutdown procedure is
registered with the operating system. For those systems that do not offer this
feature in the OS, the compiler run-time routine atexit() is used immediately
after calling INIT_XB, using IP.exitPtr as the function pointer for atexit().
ΓòÉΓòÉΓòÉ 9.4. MEMORY_XB ΓòÉΓòÉΓòÉ
Pack: MEMORYPACK Source Example
IN OUT
MP.func MP.stat
MP.memory
MP.memory returns with the number of bytes in the private memory arena
allocatable by the process according to DosQuerySysInfo for QSV_MAXPRMEM, or
as provided by the OS. It is for informational use only.
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.5. BACKUP_FILE_XB ΓòÉΓòÉΓòÉ
Pack: COPYPACK Source Example
IN OUT
CP.func CP.stat
CP.handle
CP.filenamePtr
Copy an open BULLET index file or data/memo files. BULLET repacks and
reindexes files in place, requiring less disk space to perform the function.
This routine allows a file to be safely copied for a possible later restore.
This function is recommended prior to packing a data file with
PACK_RECORDS_XB. For index files, COPY_INDEX_HEADER_XB is sufficient since
index files are easily recreated so long as you have the data file along with
the index file header.
A full-lock should be in force before copying. A shared lock may be used.
If CP.handle belongs to a DBF data file, and if a memo file is attached, the
memo file backup name is as CP.filenamePtr, the backup DBF pathname, but the
extension is always set to "._BT". For example, if CP.handle is for a DBF
that has a DBT memo file attached, then the current state of the DBF file is
copied to CP.filenamePtr, say, "\CURRBACK\ACCT.DBF", and, in this case, the
DBT memo file is copied to "\CURRBACK\ACCT._BT". The name of the original
DBF/DBT does not matter. If MEMO_EXTENSION of SET_SYSVARS_XB has changed the
default, then that extension is used on the memo copy, with '_' replacing its
first character.
To prevent the backing up of a DBT memo file when backing up a DBF data file,
set CP.handle = -CP.handle (i.e., negative CP.handle). This way, the DBT memo
file is not copied. To backup only a DBT file, close the DBF and copy the DBT
by some other means.
ΓòÉΓòÉΓòÉ 9.6. STAT_HANDLE_XB ΓòÉΓòÉΓòÉ
Pack: STATHANDLEPACK Source Example
IN OUT
SHP.func SHP.stat
SHP.handle SHP.ID
Get information on a file handle number to determine if it is a BULLET file,
and if so, its type: index or data.
SHP.ID File type
0 index, IX3 use STAT_INDEX_XB for file stats
1 data, DBF use STAT_DATA_XB for file stats
-1 unknown
Only bit0 of SHP.ID is significant if not -1. So, if bit0=0 then the handle
belongs to an index file. If bit0=1 then it's a data file.
Memo file handles return as unknown. A DBF file's memo file handle is stored
in the DBF file's data area, and is returned by STAT_DATA_XB in
SDP.memoHandle.
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.7. GET_ERROR_CLASS_XB ΓòÉΓòÉΓòÉ
Pack: XERRORPACK Source Example
IN OUT
XEP.func XEP.errClass
XEP.stat XEP.action
XEP.location
Get the extended error information for the code passed in XEP.stat. This
information includes the error classification, recommended action, and origin
of the error.
Any system error code can be specified, not necessarily the one that last
occurred. If a return code is not a BULLET code, then it is a system error
code (from the CP, DosXXX routines).
The ERRCLASS, ERRACT, and ERRLOC items below are OS/2 values, names and
descriptions for DosErrClass().
Error Classification
Value Name Description
1 ERRCLASS_OUTRES Out of resources
2 ERRCLASS_TEMPSIT Temporary situation
3 ERRCLASS_AUTH Authorization failed
4 ERRCLASS_INTRN Internal error
5 ERRCLASS_HRDFAIL Device hardware failure
6 ERRCLASS_SYSFAIL System failure
7 ERRCLASS_APPEAR Probably application error
8 ERRCLASS_NOTFND Item not located
9 ERRCLASS_BADFMT Bad format for function or data
10 ERRCLASS_LOCKED Resource or data locked
11 ERRCLASS_MEDIA Incorrect media, CRC error
12 ERRCLASS_ALREADY Action already taken or done, or resource already exists
13 ERRCLASS_UNK Unclassified
14 ERRCLASS_CANT Cannot perform requested action
15 ERRCLASS_TIME Timeout
Recommended Action
Value Name Description
1 ERRACT_RETRY Retry immediately
2 ERRACT_DLYRET Delay and retry
3 ERRACT_USER Bad user input - get new values
4 ERRACT_ABORT Terminate in an orderly manner
5 ERRACT_PANIC Terminate immediately
6 ERRACT_IGNORE Ignore error
7 ERRACT_INTRET Retry after user intervention
Origin
Value Name Description
1 ERRLOC_UNK Unknown
2 ERRLOC_DISK Disk
3 ERRLOC_NET Network
4 ERRLOC_SERDEV Serial device
5 ERRLOC_MEM Memory
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.8. QUERY_SYSVARS_XB ΓòÉΓòÉΓòÉ
Pack: QUERYSETPACK Source Example
IN OUT
QSP.func QSP.stat
QSP.item QSP.itemValue
Query a BULLET system variable.
To get the function pointers to the sort-compare functions, use:
QSP.item FuncPtr To
ASCII_SORT ASCII sort compare
NLS_SORT NLS sort compare
S16_SORT 16-bit signed integer
U16_SORT 16-bit unsigned integer
S32_SORT 32-bit signed integer
U32_SORT 32-bit unsigned integer
All intrinsic sort compares (1-6) point to the same function. They cannot be
called except by BULLET itself. The integer compare routines are based on
Intel byte order. For Motorola byte order, ASCII sort can be used for
all-positive numbers, otherwise a custom sort-compare should be used.
QSP.item FuncPtr To
10-19 Custom sort-compare functions
Before creating or opening an index file with a custom sort-compare function
(which is specified during CREATE_INDEX_XB), that function's address must
first be sent to BULLET using SET_SYSVARS_XB. Thereafter, that function must
be available whenever that index file is accessed. See Custom Sort-Compare
Function for creating custom sort-compare functions.
To get the function pointers to the build key and expression parser routines,
use:
QSP.item FuncPtr To
BUILD_KEY_FUNC Build key routine
PARSER_FUNC Key expression parser routine
Before creating or opening an index file with a custom build key or expression
parser routine (which is specified at any time, but must be used in a
consistent manner), that routine's address must first be sent to BULLET using
SET_SYSVARS_XB. Thereafter, that routine should be available since it may be
required again. See Custom Build-Key Routine for creating a custom build-key
routine and Custom Expression Parser Routine for creating a custom key
expression parser.
To get the BULLET system variables' values, use:
QSP.item Value To
26 (read-only) low-word: number of xb$Malloc(), high-word: number of xb$Free()
27 (read-only) Max instances (2, 32, 999)
28 (read-only) Max files (100, 250, 1024)
29 (read-only) Handle of Bullet's mutex semaphore
LOCK_TIMEOUT Lock file bytes timeout, in milliseconds (default=0)
MUTEX_SEM_TIMEOUT Mutex semaphore request timeout, in milliseconds (default=0)
PACK_BUFFER_SIZE Pack buffer size, in bytes (default=0: autosize)
REINDEX_BUFFER_SIZE Reindex buffer size, in bytes (default=0: autosize)
REINDEX_PACK_PCT Reindex node pack percentage, 50-100% (default=100)
TMP_PATH_PTR Temporary file path pointer (default=NULL, where TMP= used, then .\)
REINDEX_SKIP_TAG Reindex tag field character to skip (default=0, no skip)
COMMIT_AT_EACH Commit each individual file during INSERT/UPDATE_XB (default=0, defer until flush)
MEMO_BLOCKSIZE Memo file block size (default=512 bytes; minimum is 24 bytes)
MEMO_EXTENSION Memo file extension (default is "DBT\0")
MAX_DATAFILE_SIZE Max data file size-1 (default=2047MB, absolute max is 4095MB)
MAX_INDEXFILE_SIZE Max index file size-1 (default=2047MB, absolute max is 4095MB)
ATOMIC_MODE Atomic mode (bit0=1 then atomic Next and Prev access, default=0)
CALLBACK_PTR Callback routine at reindex/pack (def=0, none)
The timeout values determine if the kernel should wait for a pre-determined
time before returning an error if the resource cannot be obtained. The lock
timeout specifies how long to wait for a lock to be obtained in case some
other process has a lock on the same resource. The mutex timeout specifies
how long to wait for access to BULLET in case some other thread in this
process is in BULLET. Multiple processes can access BULLET at the same time,
but only one thread in each process can be inside BULLET at any one time.
The buffer sizes, when 0, default to a minimum reasonable size. Performance
is acceptable at these sizes. For best performance, provide as much real
memory as possible, up to 512KB. Larger buffers can be used.
The reindex node pack percentage determines how many keys are packed on a
node. 100% forces as many keys as possible, minus 1.
If the temporary file path pointer is NULL (the default), then the TMP=
environment variable is used to locate any temporary files created by BULLET,
or if that is not found, then the current directory is used. The pointer
supplied, if any, should be to a string containing an existing path (drive
should be included; a trailing '\' is optional, but recommended). See
REINDEX_XB for size requirements.
The reindex skip tag character, if encountered in the DBF record's tag field
(the first byte of each record), causes the reindex routine to not place that
record's key value into the index file. Also, BUILD_KEY_XB returns a warning
if the record supplied has a matching tag character. To disable skip tag
processing, set it to 0.
Inserts and Updates, by default, do not commit each file when that pack is
processed. Instead, it is left to the programmer to issue a FLUSH_XB to
commit. To force a commit after each pack file is processed, set CommitAtEach
to 1. This is not one single commit, but a commit for each file in the pack,
after that file has been processed, but before the next file in the pack is.
This will not prevent a roll-back should it be needed.
A memo file can have at most 589,822 blocks. At the default 512 bytes per
block, that equates to about 288MB. If you need more memo space, increase the
block size. The memo extension default is "DBT\0". Generally, it's a good
idea to leave it at this.
The maximum file sizes are enforced when adding to or reading from DBF files,
and when inserting into or reading from index files. The default is 2047 MB
(0x7FEFFFFF). If your file system permits 4GB files, set the values to 4095
MB (0xFFEFFFFF).
The Atomic mode flag determines how key access is handled. When bit0=0, the
default, the key routines, NEXT_KEY_XB, PREV_KEY_XB, and GET_NEXT_XB,
GET_PREV_XB, use the internal position of the last gotten key as their
starting point. In multi-threaded code, it's possible that another thread has
since accessed the same file handle and altered the last gotten key. By
setting bit0=1, key access (next or previous) can now specify a starting point
(typically already set up in AP.keyPtr), rather than starting at the last
accessed key for that handle (which may have been changed by another thread).
The Callback routine receives a pointer to a CALLBACK structure on the stack.
The callback routine may clean the stack (e.g., a _syscall routine). It may
also leave it for the caller to clean (e.g., a _stdcall or __cdecl routine).
See bd_rix.c on the distribution disk for an example. When the CALLBACK_PTR
== NULL (default), no callback is made. Currently, the callback is made
during REINDEX_XB and PACK_RECORDS calls, at a rate dependent on the
*_BUFFER_SIZE.
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.9. SET_SYSVARS_XB ΓòÉΓòÉΓòÉ
Pack: QUERYSETPACK Source Example
IN OUT
QSP.func QSP.stat
QSP.item QSP.itemValue
QSP.itemValue
Set a BULLET system variable, returning the previous value.
To use, set QSP.item to the item to set, and QSP.itemValue with the value to
use (function's address, variable's timeout value, etc., whatever the case may
be). On return, QSP.itemValue is the previous value that QSP.item was set to.
QSP.item FuncPtr To
ASCII_SORT ASCII sort compare
NLS_SORT NLS sort compare
S16_SORT 16-bit signed integer
U16_SORT 16-bit unsigned integer
S32_SORT 32-bit signed integer
U32_SORT 32-bit unsigned integer
All intrinsic sort compares (1-6) point to the same function. They cannot be
called except by BULLET itself. They should not be overloaded with custom
functions. If you have a custom sort-compare, use one of the custom slots.
The integer compare routines are based on Intel byte order. For Motorola byte
order, ASCII sort can be used for all-positive numbers, otherwise a custom
sort-compare should be used.
QSP.item FuncPtr To
10-19 Custom sort-compare functions
Before creating or opening an index file with a custom sort-compare function
(which is specified during CREATE_INDEX_XB), that function's address must
first be sent to BULLET using this routine. Thereafter, that function must be
available whenever that index file is accessed. See Custom Sort-Compare
Function for creating custom sort-compare functions.
To set the function pointers to the build key and expression parser routines,
use:
QSP.item FuncPtr To
BUILD_KEY_FUNC Build key routine
PARSER_FUNC Key expression parser routine
Before creating or opening an index file with a custom build key or expression
parser routine (which is specified at any time, but must be used in a
consistent manner), that routine's address must first be sent to BULLET using
this routine. Thereafter, that routine should always be ready (in a callable
state) since it may be required again. See Custom Build-Key Routine for
creating a custom build-key routine and Custom Expression Parser Routine for
creating a custom key expression parser.
To set the BULLET system variables' values, use:
QSP.item Value To
LOCK_TIMEOUT Lock file bytes timeout, in milliseconds (default=0)
MUTEX_SEM_TIMEOUT Mutex semaphore request timeout, in milliseconds (default=0)
PACK_BUFFER_SIZE Pack buffer size, in bytes (default=0: autosize)
REINDEX_BUFFER_SIZE Reindex buffer size, in bytes (default=0: autosize)
REINDEX_PACK_PCT Reindex node pack percentage, 50-100% (default=100)
TMP_PATH_PTR Temporary file path pointer (default=NULL, where TMP= used, then .\)
REINDEX_SKIP_TAG Reindex tag field character to skip (default=0, no skip)
COMMIT_AT_EACH Commit each individual file during INSERT/UPDATE_XB (default=0, defer until flush)
MEMO_BLOCKSIZE Memo file block size (default=512 bytes; minimum is 24 bytes)
MEMO_EXTENSION Memo file extension (default is "DBT\0")
MAX_DATAFILE_SIZE Max data file size-1 (default=2047MB, absolute max is 4095MB)
MAX_INDEXFILE_SIZE Max index file size-1 (default=2047MB, absolute max is 4095MB)
ATOMIC_MODE Atomic mode (bit0=1 then atomic Next and Prev access, default=0)
CALLBACK_PTR Callback routine at reindex/pack (def=0, none)
The timeout values determine if the kernel should wait for a pre-determined
time before returning an error if the resource cannot be obtained. The lock
timeout specifies how long to wait for a lock to be obtained in case some
other process has a lock on the same resource. The mutex timeout specifies
how long to wait for access to BULLET in case some other thread in this
process is in BULLET. Multiple processes can access BULLET at the same time,
but only one thread in each process can be inside BULLET at any one time.
The buffer sizes, when 0, default to a minimum reasonable size. Performance
is acceptable at these sizes. For best performance, provide as much real
memory as possible, up to 512KB. Larger buffers can be used.
The reindex node pack percentage determines how many keys are packed on a
node. 100% forces as many keys as possible, minus 1.
If the temporary file path pointer is NULL (the default), then the TMP=
environment variable is used to locate any temporary files created by BULLET,
or if that is not found, then the current directory is used. The pointer
supplied, if any, should be to a string containing an existing path (drive
should be included; a trailing '\' is optional, but recommended). See
REINDEX_XB for size requirements.
The reindex skip tag character, if encountered in the DBF record's tag field
(the first byte of each record), causes the reindex routine to not place that
record's key value into the index file. Also, BUILD_KEY_XB returns a warning
if the record supplied has a matching tag character. To disable skip tag
processing, set it to 0.
Inserts and Updates, by default, do not commit each file when that pack is
processed. Instead, it is left to the programmer to issue a FLUSH_XB to
commit. To force a commit after each pack file is processed, set CommitAtEach
to 1. This is not one single commit, but a commit for each file in the pack,
after that file has been processed, but before the next file in the pack is.
This will not prevent a roll-back should it be needed.
A memo file can have at most 589,822 blocks. At the default 512 bytes per
block, that equates to about 288MB. If you need more memo space, increase the
block size. The memo extension default is "DBT\0". Generally, it's a good
idea to leave it at this.
The maximum file sizes are enforced when adding to or reading from DBF files,
and when inserting into or reading from index files. The default is 2047 MB
(0x7FEFFFFF). If your file system permits 4GB files, set the values to 4095
MB (0xFFEFFFFF).
The Atomic mode flag determines how key access is handled. When bit0=0, the
default, the key routines, NEXT_KEY_XB, PREV_KEY_XB, and GET_NEXT_XB,
GET_PREV_XB, use the internal position of the last gotten key as their
starting point. In multi-threaded code, it's possible that another thread has
since accessed the same file handle and altered the last gotten key. By
setting bit0=1, key access (next or previous) can now specify a starting point
(typically already set up in AP.keyPtr), rather than starting at the last
accessed key for that handle (which may have been changed by another thread).
The Callback routine receives a pointer to a CALLBACK structure on the stack.
The callback routine may clean the stack (e.g., a _syscall routine). It may
also leave it for the caller to clean (e.g., a _stdcall or __cdecl routine).
See bd_rix.c on the distribution disk for an example. When the CALLBACK_PTR
== NULL (default), no callback is made. Currently, the callback is made
during REINDEX_XB and PACK_RECORDS calls, at a rate dependent on the
*_BUFFER_SIZE.
Note: Issuing INIT_XB restores all system variables (those setable via this
routine) and function pointers, but not vectors, to their default values.
This is done even if INIT_XB returns an error that BULLET has already been
initialized.
ΓòÉΓòÉΓòÉ 9.10. SET_DVMON_XB ΓòÉΓòÉΓòÉ
This routine is not currently used.
ΓòÉΓòÉΓòÉ 9.11. QUERY_VECTORS_XB ΓòÉΓòÉΓòÉ
Pack: QUERYSETPACK Source Example
IN OUT
QSP.func QSP.stat
QSP.item QSP.itemValue
Query a BULLET OS API vector.
To get the vectors that Bullet makes to access OS API calls, set QSP.item to
the desired VECTOR_* constant below. On return, QSP.itemValue has the
function pointer for that item.
QSP.item
VECTOR_CLOSE_FILE
VECTOR_CREATE_DIR
VECTOR_CREATE_FILE
VECTOR_CREATE_UNIQUE_FILE
VECTOR_DELETE_FILE
VECTOR_LENGTH_FILE
VECTOR_MOVE_FILE
VECTOR_OPEN_FILE
VECTOR_READ_FILE
VECTOR_SEEK_FILE
VECTOR_UPDATE_DIR_ENTRY
VECTOR_WRITE_FILE
VECTOR_LOCK_FILE
VECTOR_IS_DRIVE_REMOTE
VECTOR_IS_FILE_REMOTE
VECTOR_EXITLIST
VECTOR_REMOVE_EXITLIST
VECTOR_FREE
VECTOR_GET_SORT_TABLE
VECTOR_GET_COUNTRY_INFO
VECTOR_GET_ERROR_CLASS
VECTOR_GET_MEMORY
VECTOR_GET_TMP_DIR
VECTOR_GET_VERSION
VECTOR_MALLOC
VECTOR_SET_HANDLE_COUNT
VECTOR_GET_TIME_INFO
VECTOR_UPPERCASE
VECTOR_CLOSE_MUTEX_SEM
VECTOR_CREATE_MUTEX_SEM
VECTOR_RELEASE_MUTEX_SEM
VECTOR_REQUEST_MUTEX_SEM
If QSP.itemValue is returned NULL, the default Bullet OS API call is being
used.
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.12. SET_VECTORS_XB ΓòÉΓòÉΓòÉ
Pack: QUERYSETPACK Source Example
IN OUT
QSP.func QSP.stat
QSP.item QSP.itemValue
QSP.itemValue
Set a BULLET OS API vector.
To set the vectors that Bullet makes to access OS API calls, set QSP.item to
the desired VECTOR_* constant below, and QSP.itemValue to its replacement's
address. On return, QSP.itemValue has the previous function pointer for that
item.
QSP.item
VECTOR_CLOSE_FILE
VECTOR_CREATE_DIR
VECTOR_CREATE_FILE
VECTOR_CREATE_UNIQUE_FILE
VECTOR_DELETE_FILE
VECTOR_LENGTH_FILE
VECTOR_MOVE_FILE
VECTOR_OPEN_FILE
VECTOR_READ_FILE
VECTOR_SEEK_FILE
VECTOR_UPDATE_DIR_ENTRY
VECTOR_WRITE_FILE
VECTOR_LOCK_FILE
VECTOR_IS_DRIVE_REMOTE
VECTOR_IS_FILE_REMOTE
VECTOR_EXITLIST
VECTOR_REMOVE_EXITLIST
VECTOR_FREE
VECTOR_GET_SORT_TABLE
VECTOR_GET_COUNTRY_INFO
VECTOR_GET_ERROR_CLASS
VECTOR_GET_MEMORY
VECTOR_GET_TMP_DIR
VECTOR_GET_VERSION
VECTOR_MALLOC
VECTOR_SET_HANDLE_COUNT
VECTOR_GET_TIME_INFO
VECTOR_UPPERCASE
VECTOR_CLOSE_MUTEX_SEM
VECTOR_CREATE_MUTEX_SEM
VECTOR_RELEASE_MUTEX_SEM
VECTOR_REQUEST_MUTEX_SEM
Example replacement routines are in ccdosfn.c. Many of the routines in
ccdosfn.c are standard C, but some are OS-specific and must be updated for the
OS being used.
If QSP.itemValue is set to NULL, the default Bullet OS API call is used.
On return from a successful call, QSP.itemValue is the value of the previous
function pointer for that item, which may be returned NULL, indicating that
the default Bullet OS API call was being used.
ΓòÉΓòÉΓòÉ 9.13. CREATE_DATA_XB ΓòÉΓòÉΓòÉ
Pack: CREATEDATAPACK Source Example
IN OUT
CDP.func CDP.stat
CDP.filenamePtr
CDP.noFields
CDP.fieldListPtr
CDP.fileID
Create a new BULLET DBF data file with the name at CDP.filenamePtr, and an
optional DBT memo file.
Before using this routine, allocate an array of field descriptors of type
FIELDDESCTYPE, one for each field in the record (number of fields as set in
CDP.noFields). It is recommended that this allocation be zeroed before use
since fieldnames and reserved entries must be 0-filled:
FIELDDESCTYPE fieldList[12]; // 12 fields used in data record
:
memset(fieldList,0,sizeof(fieldList)); // init unused bytes to 0 (required)
Filename
The drive and path must exist if used as part of the filename. Long filenames
may be used if supported by the file system in use. As with all text strings
used by Bullet, the filename must end in a '\0'.
Number of Fields
The number of descriptors in the array, described next. Each field has a
descriptor. The tag field is not a formal field, and so has no descriptor,
and is not counted in the number of fields. The maximum number of fields is
254 according to the DBF standard. Bullet allows 1024, but 254 should be used
if creating a standard DBF III Plus file.
Field Descriptors
For each field, a descriptor is used to identify and type it. These
descriptors are assigned to an array; the pointer to that array is assigned to
CDP.fieldListPtr. The format of the descriptor follows, with a physical
format listed in DBF File Format.
Γûá Fieldname
10 characters plus null byte terminator. Valid fieldname characters are ASCII
A-Z (upper-case) and the underscore (ASCII 95). All bytes after the fieldname
must be null bytes. E.g., if the fieldname is "LNAME", five characters, the
following six bytes (including the 11th byte) are set to 0. The eleventh byte
is always a null byte since 10 characters is the maximum fieldname length.
Extended ASCII characters (above 127) should not be used.
fieldList[0].fieldname = "ANYNAME"; // see memset() above
Γûá Field type and size
Standard Xbase field types are C, D, L, M, and N:
Type Description
C Character field, any code page character, 1 to 255 characters.
Null bytes are not desirable except as a string terminator. There is
no requirement that field data be terminated with a '\0'. The field
data should be left-justified within the field, but this is not
required (in which case use leading spaces, not 0 bytes).
fieldList[0].fieldType = 'C';
fieldList[0].fieldLen = 25;
fieldList[0].fieldDC = 0;
D Date field, valid ASCII digits for date, 8 characters.
The physical format is YYYYMMDD, where YYYY is the year (1999), MM is
the month (1-12), and DD the day (1-31). The date field is always 8
bytes long, and is in ASCII digits ('19991231'). If no date, set to
all spaces.
fieldList[0].fieldType = 'D';
fieldList[0].fieldLen = 8;
fieldList[0].fieldDC = 0;
L Logical field, < SPACE> Y N T F y n t f, 1 character.
A single-byte field. When not yet initialized the value will be a
<SPACE> (ASCII 32). This is typically displayed as a '?' to the user,
indicating that the field has not been initialized. Initialized values
are variations of yes, no, true, false ('Y', 'y', etc.).
fieldList[0].fieldType = 'L';
fieldList[0].fieldLen = 1;
fieldList[0].fieldDC = 0;
M Memo field, 10 ASCII digits, 10 characters.
Field data is used as the block number in the corresponding DBT memo
file. Each block is typically 512 bytes, with the first block (block
#0) used as the memo file header. If no block is used in the .DBT by
this record, the field is set to <SPACES>. The first memo block is
stored as "0000000001". (This description is valid for dBASE IV and
later memo files, as created and used by BULLET.) Some Xbase versions
use field types B and G as variations of memo files. They are as M,
but contain general data (as in anything), while memo files contain
only text. BULLET supports any type data in its memo files, and you may
use the CDP.fieldType of 'B' or 'G'.
More than one memo field per record is permitted. For example, you may
need a memo for the printable address, where the address is free-form
rather than in separate fields (i.e., you have both forms), and another
memo for general notes, and yet a third for problem reports, and so on.
All these, and all memos for the rest of the DBF file, are stored in
the same DBT memo file.
Note: BULLET does not use the fieldType with regard to identifying
memo field type; it is the programmer's responsibility to check the
fieldType and act on it accordingly, such as adding memo numbers.
fieldList[0].fieldType = 'M';
fieldList[0].fieldLen = 10;
fieldList[0].fieldDC = 0;
N Numeric field, ASCII digits, 19 digits maximum (see below).
All standard Xbase data is stored in ASCII form (for universal
exchange). Numeric fields are to be right-justified, with leading
spaces, and an aligned decimal point, if any (relative this field in
other records). Do not end the field with a null byte.
The total size of the numeric field is specified in .fieldLen, which
includes any leading sign, the decimal point, and decimal digits to the
right of the decimal point (if any decimal point). The maximum total
size is 19 places. If a decimal point, then the number of digits to
the right may be from 1 to 15 digits, but must be no more than the
total-2.
FieldLen.FieldDC Example
8.2 " 2345.78"
8.2 "12345.78"
8.2 "-2345.78"
8.1 "123456.8"
8.0 "12345678"
5.3 "2.235"
5.4 (not valid)
fieldList[0].fieldType = 'N';
fieldList[0].fieldLen = 8;
fieldList[0].fieldDC = 2;
Although not dBASE compatible, you may use binary fields in your data records.
The Xbase standard always has ASCII data in the data fields, even if the field
is numeric. For example, an 'N' type field of 8.2 (total
length.decimal-count) is stored as an ASCII text string in the data record,
say, a string like " 1100.55". If you want dBASE compatibility your field
data must also be ASCII. However, if you can forgo this requirement, you can
use binary values in the fields.
To do this you must specify a field type of 'Y' (actually, anything but an
'N') and, if it is to be used as a key field, also set the sort function to
the appropriate type (S16_SORT, etc.). The field length
(fieldList[x].fieldLen) for a 'Y' field type is 2 if 16-bit, and 4 if 32-bit.
Also possible is floating-point (with a custom sort-compare function). A
likely field type marker for this would be 'F'. Note that both 'Y' and 'F'
are completely non-standard Xbase types, and only your programs will
understand them.
Note: 'B' should not be used as a binary field type marker since dBASE V uses
'B' to signify a binary-data memo file field. Bullet makes no distinction in
its memo file data; anything can be placed in them. Typically, your memo
fields are marked as 'M' in Bullet, but could also be 'B' or 'G'.
File ID
Conventional dBASE DBF files have a CDP.fileID=3. To create a memo file (DBT,
dBASE IV compatible), set CDP.fileID=x8B. For the DBT to be created, both
bits 3 and 7 (0x88) must be set. The other bits may be anything, and are not
checked. In creating your DBF files, specify CDP.fileID=3 to ensure
compatibility across Xbase versions, and limit record length to 4000
characters. If creating a non-standard DBF (e.g., non-standard field types,
extended field lengths, etc.) it's recommended to use CDP.fileID=0 or
CDP.fileID=1. For a standard DBF file with a memo file (dBASE IV or later),
use CDP.fileID=0x8B (eight-bee).
Generally, field data is space-filled. String terminators are allowed in
C-haracter field types, but should not be used in other fields.
Memo File Creation
If bits 3 and 7 are set in CDP.fileID, a memo file is created for the DBF. The
memo filename will be the same as the DBF name except the extension. The memo
file is created after the DBF, with a block size of 512 bytes, and filename
extension of ".DBT". The default block size and extension can be overridden
(see SET_SYSVARS_XB) prior to calling this routine.
Note: A simple way to check that your record description in the field list
matches your source code structure is to compare the number of bytes used by
all fields in the field list (+1 for the delete tag byte) with the size of
your program's structure. They must equal. Also verify each field's size to
make sure they match. By doing this you prevent the problem where data on
disk does not match the data in your record structure.
ΓòÉΓòÉΓòÉ 9.14. OPEN_DATA_XB ΓòÉΓòÉΓòÉ
Pack: OPENPACK Source Example
IN OUT
OP.func OP.stat
OP.filenamePtr OP.handle
OP.asMode
Open an existing DBF data file for use. For DBF opens, two parameters are
specified: the filename and the access-sharing mode. The OP.xbLink parameter
is used only for index opens, and so is not used here.
The OP.asMode has optional cache mode settings. The caching modes cover
locality, write-through, and skip cache. Locality is typically mostly random
(RND_LOCALITY), but may be mostly sequential if the data file has been sorted
and the index file recently reindexed and processing is mostly in-order (first
to last, rather than random). Locality is used to tune the cache. Also,
normally, data is written to the cache with control returning immediately to
the program before the disk is written (an asynchronous write). To force the
write to take place before control is returned (a synchronous write), use the
WRITE_THROUGH mode. To skip the cache completely, use the SKIP_CACHE mode.
This, as all OP.asMode settings, affects this file handle only.
On a successful open, the file handle is returned. Use this handle for all
further access to this file. If the DBF was created with a compatible memo
file, it is also opened. The handle of the memo file is available via
STAT_DATA_XB, but all access to the memo file is made with the handle of the
memo file's master DBF (the handle returned by this routine in OP.handle).
The memo file is opened using the same OP.asMode.
Note: FoxPro DBF files with Fox memo files (FPT) use an ID of 0xFF. Bullet
does not support FoxPro memo files, and so opening a FoxPro DBF with a Fox
memo file returns the warning message, WRN_CANNOT_OPEN_MEMO. The DBF file is
opened, and the warning can be ignored.
Once open, you can get information on the data file by using STAT_DATA_XB.
Each DBF data file opened allocates and commits at least 4K bytes for internal
use:
Number of Fields Memory
1 to 121 4KB
122 to 249 8KB
each 128 fields 4KB more
This memory is released when you close the file with CLOSE_DATA_XB. or issue
EXIT_XB.
Note: You must open the data file before you can open or create any of its
index files.
When BULLET creates a DBF, it forces all fieldnames to upper-case (it's a DBF
requirement) and 0-fills them as well. On data file opens (OPEN_DATA_XB), it
also does this, and so any header copy (COPY_DATA_HEADER_XB) will have
upper-cased fieldnames (the original file is not changed). To prevent BULLET
from mapping the fieldnames to upper-case (NLS mapping, though fieldnames
should be standard ASCII characters only), set bit31 of OP.asMode to 1
(0x80000042, for example). This skips the case mapping. Zero-filling always
takes place, and starts after the first '\0' byte in the fieldname.
ΓòÉΓòÉΓòÉ 9.15. CLOSE_DATA_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Close an existing data file.
Closing the file updates the file header and releases the memory used by the
file. Any associated memo file is closed, too. Any outstanding locks should
be unlocked before calling this routine.
Note: Remaining locks belonging to this handle are released by the OS upon
the successful close.
ΓòÉΓòÉΓòÉ 9.16. STAT_DATA_XB ΓòÉΓòÉΓòÉ
Pack: STATDATAPACK Source Example
IN OUT
SDP.func SDP.stat SDP.recordLength
SDP.handle SDP.fileType SDP.xactionFlag
SDP.flags SDP.encryptFlag
SDP.progress SDP.herePtr
SDP.morePtr SDP.memoHandle
SDP.fields SDP.memoBlockSize
SDP.asMode SDP.memoFlags
SDP.filenamePtr SDP.memoLastRecord
SDP.fileID SDP.memoLastSize
SDP.lastUpdate SDP.lockCount
SDP.records
Return information BULLET has on the DBF data file specified by SDP.handle.
Item Description
stat Return code of operation
fileType 1 for DBF
flags Bit0=1 if file has changed since last flush (dirty)
Bit1=1 if the file has its entire region locked (full lock)
Bit2=1 if the file has a shared lock in use (cannot write
to it if so)
progress Percentage of pack operation completed, 1-99, or 0 if done
morePtr Always 0
fields Number of fields per record (does not included implicit tag
field)
asMode Access-sharing-cache mode as specified at open (excludes
NoCaseMap bit31)
filenamePtr Pointer to the filename as used in OPEN_DATA_XB
fileID ID byte used when the DBF was created (the first byte of
the file)
lastUpdate Date of last change (binary: high word=year (1999), low
byte=day, high byte=month)
records Number of records in the DBF (includes any delete-tagged
records)
recordLength Total length of a data record, including tag field
xactionFlag Not currently used
encryptFlag Not currently used
herePtr Pointer to the internal data control area for this file
handle
memoHandle Handle of open memo file (0 if none)
memoBlockSize Memo file block size (512 is typical, 24 is minimum)
memoFlags Bit0=1 dirty
memoLastRecord Last accessed memo record (0 if none; same as 'block
number')
memoLastSize Size of last accessed memo record (in bytes, including 8
bytes overhead)
lockCount Number of full-locks in force (locked on first, unlocked on
last)
Typically, your program tracks whether a particular handle belongs to an index
file or data file. In cases where this is not possible, call the
STAT_HANDLE_XB. routine to determine what file type a handle is.
Note: In network environments, you should have an exclusive lock on the data
file (and implicitly, therefore, the memo file, if any) before using this
routine to ensure that the information is current. This also applies to
multi-process environments on a single machine. This routine is not
mutex-protected. During the call, the file handle must not be closed by
another thread.
ΓòÉΓòÉΓòÉ 9.17. READ_DATA_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Reload the disk copy of the data header for the opened DBF data file handle,
refreshing the in-memory copy. Any associated memo file is refreshed, too.
Normally, this routine is not called directly but rather is done automatically
when you full-lock the file (LOCK_XB). This routine does not refresh the
header if the current state is dirty (SDP.flags, bit0=1); it returns an error
if tried. Since it is recommended that a full-lock be in force before using
this routine (shared or exclusive), and since a full-lock always reloads the
header anyway, calling this routine should never be required. If ever there
is a reason to use this routine without having a full-lock in force, then, of
course, you may need to. However, it is not wise to reload the header without
a full-lock (which locks the header). If you are using your own lock
routines, this call will be very useful.
In single-user, single-tasking systems this routine is not needed. However,
in a multi-user or multi-tasking system it's possible, and desirable, for two
or more programs to use the same data file. Consider this scenario: A data
file has 100 records. Two programs access this data file, both opening it.
Program 1 locks the file, adds a new record, then flushes and unlocks the
file. Program 1 knows that there are now 101 records in the file. However,
Program 2 is not aware of the changes that Program 1 made--it thinks that
there are still 100 records in the file. This out-of-sync situation is easily
remedied by having Program 2 reload the data header from the file on disk.
How does Program 2 know that it needs to reload the header? It doesn't.
Instead, BULLET uses a simple, yet effective, approach when dealing with this.
When BULLET full-locks a file, BULLET automatically reloads the header by
using this routine. When removing the full-lock, BULLET automatically flushes
the header using FLUSH_DATA_HEADER_XB (unless the current lock is a shared
lock (SDP.flags bit2=1)).
ΓòÉΓòÉΓòÉ 9.18. FLUSH_DATA_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Write the in-memory copy of the data header for the opened DBF data file
handle to disk. The actual write occurs only if the header has been changed
(the dirty bit is set). Any associated memo file is flushed, too. This
routine ensures that the data header on disk matches exactly the data header
that is being maintained by BULLET.
Normally, this routine is not called directly but rather is done automatically
when you unlock the file (UNLOCK_XB). This routine does not write out the
header if the current lock state is shared (SDP.flags, bit2=1); it returns an
error if tried. Unlocking a full-lock performs a flush automatically, and so
you may never need to explicitly call this routine. Also, when relocking from
an exclusive full-lock to a shared full-lock, an automatic flush is performed.
Assume the following: A data file with 100 records. Your program opens the
data file and adds 1 record. Physically, there are 101 records on disk.
However, the header image of the data file on disk still reads 100 records.
This isn't a problem, BULLET uses its internal copy of the data header and the
internal copy does read 101 records. But, if there were a system failure now,
the image of the header would not get updated since the disk image is written
only on a CLOSE_ or FLUSH_DATA_XB, or on EXIT_XB (and also prior to
PACK_RECORDS_XB). After the system restarts, BULLET opens the file, reads the
header and thinks that there are 100 records. You lost a record. Now, if
after that record add your program issues FLUSH_DATA_HEADER_XB, the header on
disk is refreshed with the in-memory copy, keeping the two in sync. This
routine also updates the directory entry for the file, keeping things neat
there (file size). Still, it doesn't come without cost: flushing takes
additional time, therefore, you may elect to flush periodically, or whenever
the system is idle.
Note: You should have a full-lock on the file before using this routine.
ΓòÉΓòÉΓòÉ 9.19. COPY_DATA_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: COPYPACK Source Example
IN OUT
CP.func CP.stat
CP.handle
CP.filenamePtr
Copy the DBF file structure of an open data file to a new file.
This routine makes it easy for you to duplicate the structure of an existing
DBF file without having to specify all the information needed by
CREATE_DATA_XB. The resultant DBF will be exactly like the source, including
number of fields and field descriptions, and an empty memo file, if
applicable. It contains 0 records. It may be opened as a regular Bullet data
file.
A typical use for this is to create a work file, where only a subset of
records is required. For example: You want to process all records of those
whose last name starts with A. Copy the header to a work file, use GET_XB
routines to get records meeting the criterion, writing those that fit the
criterion to the work file (using either Add/Reindex, or Insert). A new index
can be specified, or an existing index can be copied using
COPY_INDEX_HEADER_XB.
ΓòÉΓòÉΓòÉ 9.20. ZAP_DATA_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Delete all records in a DBF data file.
This routine is similar to COPY_DATA_HEADER_XB except for one major
difference: All data records in the source file are physically deleted. No
action is performed on the DBF's memo file, if any.
If you have a DBF file with 100 records and use ZAP_DATA_HEADER_XB on it, all
100 records will be physically deleted and the file truncated as if no records
were ever in the file. All data records are lost forever.
ΓòÉΓòÉΓòÉ 9.21. CREATE_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: CREATEINDEXPACK Source Example
IN OUT
CIP.func CIP.stat
CIP.filenamePtr
CIP.keyExpPtr
CIP.xbLink
CIP.sortFunction
CIP.codePage
CIP.countryCode
CIP.collatePtr
CIP.nodeSize
Create a new BULLET index file.
Before you can create an index file, you must first have opened (and have
created if necessary) the BULLET DBF data file that it is to index. To open
the data file, use OPEN_DATA_XB. To create the index file, you need to
provide the name to use, the key expression, the DBF file link handle
(obtained from the OPEN_DATA_XB call), sort function/flags, and optionally,
the code page, country code, and collate table. There's also a node size
parameter. Select 512, 1024, or 2048 bytes.
Note: BULLET has an optional external data mode where only indexing is done
-- no data file link is used. In this mode, BULLET manages the index files of
the key and key data you provide (key data is any 32-bit item, e.g., a record
number, offset, etc.). This would be useful for indexing non-DBF files, even
files with variable-length records.
Filename
The drive and path must exist if used as part of the filename. Long filenames
may be used if supported by the file system in use.
Key Expression
The key expression is an ASCIIZ string composed of the elements that are to
make up this index file's key. The key can be composed of any or all of the
fields in the DBF data record, or sub-strings within any of those fields. Up
to 16 component parts can be used in the expression.
Two functions are supported in evaluating a key expression. These are SUBSTR()
and UPPER():
SUBSTR() extracts part of a field's data starting at a particular position for
x number of characters. The first position is 1.
UPPER() converts all lower-case letters to their upper-case equivalent. Since
BULLET supports NLS, UPPER() conversion is not required for proper sorting of
mixed-case text strings.
Any name used in the key expression must be a valid field name in the DBF data
file. Below are a few sample key expressions for the given data file
structure:
Name Type Len DC
FNAME C 25 0
LNAME C 25 0
SSN C 9 0
DEPT N 5 0
A few example key expression strings for this structure:
keyExpression[]="LNAME";
keyExpression[]="LNAME+FNAME";
keyExpression[]="SUBSTR(LNAME,1,4)+SUBSTR(FNAME,1,1)+SUBSTR(SSN,6,4)";
keyExpression[]="UPPER(LNAME+FNAME)"; // for ASCII sort function only
keyExpression[]="DEPT+SSN";
In the last example above, even though DEPT is a numeric field type (N), it
can still be used as a component of a multi-part character key with SSN (whose
type is set to character). This because numeric fields in dBASE DBF data
files are ASCII digits, not binary values, and are sorted according to the
ASCII value or NLS weight.
The key expression is parsed when the index file is created (this routine) and
also when reindexed (REINDEX_XB.). The parser() function, which parses the key
expression, may be replaced by a programmer-supplied function if additional
functionality is needed. See Custom Expression Parser Routine for details.
DBF File Link Handle (xbLink)
Since BULLET evaluates the key expression when the file is created (this
routine) or during reindex, it must have access to the DBF file to verify that
the key expression is valid. You must, therefore, supply the OS file handle
of the opened DBF data file. If you later change the structure of the DBF
data file (add new fields, remove others, etc.), you must use the reindex
routine to re-evaluate the key expression. If the key expression is no longer
valid after the data file changes (key field has changed names, etc.), then
you must create a brand new index file with this routine, supplying the new
key expression, rather than reindexing.
Note: Handles 0-2 are reserved handles and should never be used for any
BULLET routine. Also, .xbLink of -1 is reserved by BULLET to indicate an
external data index for index create and open routines.
Sort Function
The sort function specifies the sort method for the index file. Essentially,
this defines the compare function used by the access methods employed by
BULLET when doing any type of key access (reading and writing). There are six
intrinsic sort compare functions available, with an additional 10 sort compare
functions that can be specified by the programmer (see Custom Sort-Compare
Functions).
While not recommended, duplicate key values are supported and managed by
BULLET. The flag DUPS_ALLOWED is OR'ed with the sort function value to
specify this. Generally, it is not acceptable to allow duplicate keys for an
index; there should be one key identifying one record without any further
investigation needed to determine if the key is indeed for that record. This
is not possible, not consistently so, when duplicate keys exist. It is much
simpler to define your key so that duplicates are not generated, than it is to
deal with duplicate keys once you have them. If an attempt to insert a key
that already exists in the index file is made, and DUPS_ALLOWED was not
specified when the index file was created, the insert fails (either a
STORE_KEY_XB, an INSERT_XB, or a REINDEX_XB operation), and error
EXB_KEY_EXISTS is returned.
For Windows, the sort function flag, USE_ANSI_CHARSET may be specified. This
instructs Bullet to use the ANSI character set (i.e., Windows character set)
for the current system code page. The default is USE_OEM_CHARSET, which is
used by MS-DOS and OS/2.
Only data contained within a record should be used to build a key. The
physical record number is not part of the data of a record since it can change
at any time without you knowing about it (during a pack, for example). Do not
use the record number in an attempt to generate unique keys. Only use what is
available in the data record itself, so that the key can be built, or rebuilt,
at any time.
The intrinsic sort compare functions of BULLET are:
ASCII_SORT 1 - ASCII (up to 16 key components)
NLS_SORT 2 - NLS (up to 16 key components)
S16_SORT 3 - 16-bit signed integer (single component)
U16_SORT 4 - 16-bit unsigned integer (single component)
S32_SORT 5 - 32-bit signed integer (single component)
U32_SORT 6 - 32-bit unsigned integer (single component)
To expand on the basic functionality provided by BULLET, you can supply your
own parser, build, and sort compare routines, and have BULLET use them
instead. With your own routines in place, you can have BULLET do just about
anything with regard to the index file, including evaluating the key
expression dynamically; using more components; allowing multi-part binary
keys; and more.
Generally, character data (type C) is left-justified, and unused space is
padded with the SPACE character (ASCII 32). It is permissible to use C-type
strings, or to 0-fill unused space.
Numeric data (type N) is right-justified, with leading space to be padded with
the SPACE character. It is not permissible to use 0-fill leading bytes
(literal '0' can used, however). Since the field is right-justified, it is
not generally desirable to terminate the field with a 0 byte, either. If a
decimal count is specified (not 0),the decimal point location is to be the
same for all entries in this field. The field description must match the
actual data: If the field length and field decimal count was specified as
10.2 (10 total bytes, 2 digits to the right of the decimal, then the data is
to be formatted so that '-234567.90' is the longest data that is to be entered
in that field. All entries in all records for this field must be of the same
format. For example, " 987654.21", or " 23.01", or " -1.99" (note the
leading spaces). Numeric data is indexed as ASCII values (i.e., the key
remains character digits) unless a binary sort function is specified.
Using one of the binary integer sort compare functions requires the following:
1. Single component expression.
2. Field type must be N if the field has ASCII digits, or if the data is
binary, then the field type must be Y (actually, anything but N).
3. If ASCII digits, the value must fit into the function size (-32768 to
32767 or 0-65535 for signed/unsigned 16-bit; 2,147,483,647 to
-2,147,483,648 or 0-4,294,967,295 for 32-bit signed/unsigned values).
Although not dBASE compatible, you may use binary fields in your data records.
The Xbase standard always has ASCII data in the data fields, even if the field
is numeric. For example, an 'N' type field of 8.2 (total
length.decimal-count) is stored as an ASCII text string in the data record,
say, a string like " 1100.55" (there is no \0 string terminator). If you want
dBASE compatibility, your field data must be ASCII. However, if you can forgo
this requirement, you can use binary values in the fields.
To do this you must specify a field type of 'Y' (actually, anything but an
'N') and, if it is to be used as a key field, also set the sort function to
the appropriate type (S16_SORT, etc.). The field length
(fieldList[x].fieldLen) for a 'Y' field type is 2 if 16-bit, and 4 if 32-bit.
For 64-bit integers, a custom sort-copmare function is required since there is
no intrinsic 64-bit function available.
Note: 'B' should not be used as a binary field type marker since dBASE V uses
'B' to signify a binary-data memo file field. Bullet makes no distinction in
its memo file data; anything can be place in them. Typically, your memo
fields are marked as 'M' in Bullet, but could also be 'B' or 'G'.
The key expression string you specify may be up to 159 characters, and
evaluate out to 64 bytes (62 bytes if DUPS_ALLOWED is specified). The
expression string must be 0-terminated, as are all strings used by BULLET
itself (filenames, etc.).
National Language Support (NLS)
National Language Support is available to properly sort most languages'
alphabets. BULLET uses NLS to build the collate sequence table (sort table)
that it uses to ensure proper sorting of mixed-case keys as well as the
sorting of foreign language alphabets which use extended-ASCII. In order for
BULLET to use the proper collate table, it must know what code page and
country code to use. If not supplied, Bullet gets this information directly
from the OS, which provides the cc/cp for the current process. If you supply
cc/cp, the code page must be loaded or an error is returned (see OS/2's CHCP
command). The collate table generated is made part of the index file so that
all subsequent access to the index file maintains the original sort order,
even if, say, the MIS shop is moved to another location/computer system using
another country code/code page. These three items are discussed below.
Code Page
To use the default code page of the current process, specify a code page of 0.
The OS is queried for the current code page and this code page is then used.
Any valid and available code page can be specified. This is used only if a
custom sort-compare or NLS sort is specified.
Country Code
To use the default country code of the current process, specify a country code
of 0. The OS is queried for the current country code and this code is then
used. Any valid country code can be specified. This is used only if a custom
sort-compare or NLS sort is specified.
Custom Collate Table
If a null-pointer is specified, and a custom sort-compare or NLS sort is
specified, BULLET queries the OS for the collate sequence table to use based
on the code page and country code specified. Otherwise, the supplied table is
used. Intrinsic sorts other than NLS use no collate table, and the country
code or code page are not used, either.
To use a sort weight table of your own choosing, supply a non-NULL pointer to
this parameter. If non-NULL, the passed table is used for sort compares. The
table is composed of 256 weight values, one per character. For example, table
position 65 ('A') and table position 97 ('a') could both be weighted 65, so
that that each are considered equal when sorted. If a custom sort-compare
function was specified, this sort table may, or may not, be used -- it depends
on whether the sort compare function uses the table (it's all up to the custom
sort-compare function's logic).
Typically, you set both the code page and country code = 0, and the collate
table pointer to NULL.
Node Size
The index file is read and written in node-size chunks. The larger the node
size, the more keys are read or written per chunk. Generally, a smaller node
size offers better random key access, while a larger node size offers better
sequential key access.
Typically, an average node utilizes 66% of the node space for keys (a very
small number may contain only a few keys, while some may be filled
completely). In a 512-byte node file, for a key length of 8, there is room
for (512-5)/(keylength+8) nodes, or 31 keys. Since a typical node is filled
to 66%, that means about 20 keys per node. For a 2048-byte node file, same
parameters, there is room for (2048-5)/(keylength+8), or 127 keys. At the
standard 66% load, there are typically 83 keys per 2K node. That's 3 more
keys per 2K of disk than the 512-byte node gives for 4 nodes (20 keys*4), The
trade-off is that each node is 4 times as large, and so requires 4 times more
searching. Actual performance differences may be minimal, or may be great.
Run tests on expected data to determine the best for the data and access used.
ΓòÉΓòÉΓòÉ 9.22. OPEN_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: OPENPACK Source Example
IN OUT
OP.func OP.stat
OP.filenamePtr OP.handle
OP.asMode
OP.xbLink
Open an existing index file for use. For index opens, three parameters are
specified: the filename, the access-sharing mode, and the handle of the open
DBF file that this file indexes. It is required to open the data file before
you can open its related index file.
Note: Handles 0-2 are reserved handles and should never be used for any
BULLET routine. Also, .xbLink of -1 is reserved by BULLET to indicate an
external data index for index create and open routines.
On a successful open, the file handle is returned. Use this handle for all
further access to this file.
Once open, you can get information on the index file by using STAT_INDEX_XB.
Each index file that you open allocates and commits 4K bytes for internal use
(this will vary if SET_VECTORS_XB with VECTOR_MALLOC is used). This memory is
released when you close the file with CLOSE_INDEX_XB or issue EXIT_XB, or your
program terminates.
The OP.asMode has optional cache mode settings. The caching modes cover
locality, write-through, and skip cache. Locality is typically mostly random
(RND_LOCALITY), but may be mostly sequential if the data file has been sorted
and the index file recently reindexed and processing is mostly in-order (first
to last, rather than random). Locality is used to tune the cache. Also,
normally, data is written to the cache with control returning immediately to
the program before the disk is written (an asynchronous write). To force the
write to take place before control is returned (a synchronous write), use the
WRITE_THROUGH mode. To skip the cache completely, use the SKIP_CACHE mode.
This, as all OP.asMode settings, affects this file handle only.
BULLET has an optional external data mode where only indexing is done -- no
data file link is used. In this mode, BULLET manages the index files of the
key and key data you provide (key data is any 32-bit item, e.g., a record
number, offset, etc.). This would be useful for indexing non-DBF files, even
files with variable-length records. Only those routines that do not access
the data file may be used (any routine using AP.recPtr, for example INSERT_XB,
could not be used, but NEXT_KEY_XB, STORE_KEY, etc., may).
ΓòÉΓòÉΓòÉ 9.23. CLOSE_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Close an open index file.
Closing the file updates the file header and releases the memory used by the
file. Any outstanding locks should be unlocked before calling this routine.
Note: Remaining locks belonging to this handle are released by the OS upon
the successful close.
ΓòÉΓòÉΓòÉ 9.24. STAT_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: STATINDEXPACK Source Example
IN OUT
SIP.func SIP.stat SIP.keyLength
SIP.handle SIP.fileType SIP.keyRecNo
SIP.flags SIP.keyPtr
SIP.progress SIP.herePtr
SIP.morePtr SIP.codePage
SIP.xbLink SIP.countryCode
SIP.asMode SIP.CTptr
SIP.filenamePtr SIP.nodeSize
SIP.fileID SIP.sortFunction
SIP.keyExpPtr SIP.lockCount
SIP.keys
Return information BULLET has on the index file specified by SIP.handle.
Item Description
stat Return code of operation
fileType 0 for index, IX3
flags Bit0=1 if file has changed since last flush (dirty)
Bit1=1 if the file has its entire region locked (full lock)
Bit2=1 if the file has a shared full-lock in use (cannot
write to it if so)
progress Percentage of reindex operation completed, 1-99, or 0 if
done
morePtr Always 0
xbLink handle of the open DBF file this file indexes
asMode Access-sharing-cache mode as specified at open
filenamePtr Pointer to the filename as used in OPEN_INDEX_XB
fileID '31ch' (the first four bytes of the file)
keyExpPtr Pointer to the key expression used when the index was
created
keys Number of keys in the index file
keyLength Length of the key, including 2-byte enumerator if
DUPS_ALLOWED
keyRecNo The DBF record number that the last accessed key indexes
keyPtr Pointer to the last accessed key (valid for keyLength
bytes)
herePtr Pointer to the internal data control for this file
codePage Code page used when the index file was created (the actual
code page)
countryCode Country code used when the index file was created (the
actual country code)
CTptr Pointer to the collate sequence table used for NLS sorting
(each index file has its own sequence table) or NULL if not
an NLS index
nodeSize Size of an index node, 512, 1024, or 2048 bytes, as
specified during create
sortFunction The index sort-compare method (low word) and the sort flags
(high word), with sort-compare values 1-9 being intrinsic,
and 10-19 being custom functions; the DUPS_ALLOWED flag is
bit0 of the high word (allowed if set)
lockCount Number of full-locks in force (locked on first, unlocked on
last)
Typically, your program tracks whether a particular handle belongs to an index
file or a data file. In cases where this is not possible, call the
STAT_HANDLE_XB routine to determine what file type a handle is.
Note: In network environments, you should have an exclusive lock on the index
file before using this routine to ensure that the information is current.
This routine is not mutex-protected. During the call, the file handle must
not be closed by another thread.
ΓòÉΓòÉΓòÉ 9.25. READ_INDEX_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Reload the disk copy of the index header for the opened index file handle,
refreshing the in-memory copy.
Normally, this routine is not called directly but rather is done automatically
when you full-lock the file (LOCK_XB). This routine does not refresh the
header if the current state is dirty (SIP.flags, bit0=1); it returns an error
if tried. Since it is recommended that a full-lock be in force before using
this routine (shared or exclusive), and since a full-lock always reloads the
header anyway, calling this routine should never be required. If ever there
is a reason to use this routine without having a full-lock in force, then, of
course, you may need to. However, it is not wise to reload the header without
a full-lock (which locks the header). If you are using your own lock
routines, this call will be very useful.
In single-user, single-tasking systems this routine is not needed. However,
in a multi-user or multi-tasking system it's possible, and desirable, for two
or more programs to use the same data file. Consider this scenario: An index
file has 100 keys. Two programs access this index file, both opening it.
Program 1 locks the file, adds a new key, then flushes and unlocks the file.
Program 1 knows that there are now 101 keys in the file. However, Program 2
is not aware of the changes that Program 1 made--it thinks that there are
still 100 keys in the file. This out-of-sync situation is easily remedied by
having Program 2 reload the index header from the file on disk.
How does Program 2 know that it needs to reload the header? It doesn't.
Instead, BULLET uses a simple, yet effective, approach when dealing with this.
When BULLET full-locks a file, it automatically reloads the header using this
routine. When removing the full-lock, BULLET automatically flushes the header
using FLUSH_INDEX_HEADER_XB (unless the current lock is a shared lock
(SIP.flags bit2=1)).
ΓòÉΓòÉΓòÉ 9.26. FLUSH_INDEX_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Write the in-memory copy of the index header for the opened index file handle
to disk. The actual write occurs only if the header has been changed. This
ensures that the index header on disk matches exactly the index header that is
being maintained by BULLET.
Normally, this routine is not called directly but rather is done automatically
when you unlock the file (UNLOCK_XB). This routine does not write out the
header if the current lock state is shared (SIP.flags, bit2=1); it returns an
error if tried. Unlocking a full-lock performs a flush automatically, and so
you may never need to explicitly call this routine. Also, when relocking from
an exclusive full-lock to a shared full-lock, an automatic flush is performed.
Assume the following: An index file with 100 keys. Your program opens the
index file and adds 1 key. Physically, there are 101 keys on disk. However,
the header image of the index file on disk still reads 100 keys. This isn't a
problem; BULLET uses its in-memory copy of the index header and the in-memory
copy does read 101 keys. But, if there were a system failure after the key
add, the disk image of the header would not get updated since the disk image
is written only on a CLOSE_ or FLUSH_INDEX_XB, or on EXIT_XB (and also prior
to REINDEX_XB). After the system restarts, BULLET opens the file, reads the
header and thinks that there are 100 keys. You lost a key. Now, if after
that key was added, your program issues a FLUSH_INDEX_HEADER_XB, the header on
disk is refreshed with the in-memory copy, keeping the two in sync. The
routine updates the directory entry, keeping things neat there as well (file
size). Still, it doesn't come without cost: flushing will take additional
time, therefore, you may elect to flush periodically, or whenever the system
is idle.
Note: You should have a full-lock on the file before using this routine.
ΓòÉΓòÉΓòÉ 9.27. COPY_INDEX_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: COPYPACK Source Example
IN OUT
CP.func CP.stat
CP.handle
CP.filenamePtr
Copy the index file structure of an open index file to another file.
This routine duplicates the structure of an existing index file without having
to re-specify the information needed by CREATE_INDEX_XB. The resultant index
file will be exactly like the source, including sort function and key
expression. It contains 0 keys.
ΓòÉΓòÉΓòÉ 9.28. ZAP_INDEX_HEADER_XB ΓòÉΓòÉΓòÉ
Pack: HANDLEPACK Source Example
IN OUT
HP.func HP.stat
HP.handle
Delete all keys from an index file.
This routine is similar to COPY_INDEX_HEADER_XB except for one major
difference: All keys in the source file are physically deleted.
If you have an index file with 100 keys and issue ZAP_INDEX_HEADER_XB, all 100
keys will be physically deleted and the file truncated to 0 keys. REINDEX_XB
can be used to rebuild the index file.
Note: Since BULLET reindexes in place, the use of ZAP is not typically
needed.
ΓòÉΓòÉΓòÉ 9.29. GET_DESCRIPTOR_XB ΓòÉΓòÉΓòÉ
Pack: DESCRIPTORPACK Source Example
IN OUT
DP.func DP.stat
DP.handle DP.fieldNumber
DP.fieldNumber DP.fieldOffset
-or- DP.FD.fieldName
DP.FD.fieldName DP.FD.fieldType
DP.FD.fieldLen
DP.FD.fieldDC
DP.FD.altFieldLength
Get the field descriptor information for a field by fieldname or by field
position.
To get descriptor info by fieldname, set DP.fieldNumber=0 and set the
DP.FD.fieldname member to the fieldname string. Fieldnames must be
0-terminated and 0-filled, and must be upper-case, with A-Z and _ valid
fieldname characters. If the string matches a fieldname in the DBF descriptor
area, that field's descriptor info is returned in DP.FD, (FD is
FIELDDESCTYPE), and its position is returned in FD.fieldNumber and
FD.fieldOffset.
To get descriptor info by field position (i.e., field number), set
DP.fieldNumber to the field's position. The first is field #1. The "delete
tag" field is not considered a field. If the position is valid (i.e., greater
than 0 and not beyond the last field),that field's descriptor info is returned
in DP.FD.
This routine lets you work with unknown DBF files -- those created by another
program. By reading each field descriptor, by number, from 1 to number of
fields (SDP.noFields), you can generate a run-time layout of the DBF file.
Alternatively, you can get input from your user for a fieldname, and locate
the descriptor by name.
Note: If you need to add or delete a field, be sure to reindex all related
index files so that their key expressions can be re-evaluated. To do this, you
need to create a new data file and build it as you build any other new data
file. Then, copy record-by-record from the old DBF to the new, using the old
record layout for reads, and the new record layout for writes. After this,
reindex any index file related to the DBF file. The old DBF file can then be
deleted.
If non-standard fields are used (i.e., non-char structure members to match
non-ASCII data fields in your non-standard DBF), then be aware that your
compiler more than likely will add padding to align on member-size boundaries.
This will result in a mis-match between your compiler structure and your DBF
structure (as described in fieldList[]). To prevent this, place #pragma
pack(1) / #pragma pack() around your structures that BULLET uses. Consult
your particular compiler for alternate methods if it does not support #pragma
pack.
ΓòÉΓòÉΓòÉ 9.30. GET_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle *AP.recPtr
AP.recNo
AP.recPtr
Get the physical record from the data file into a data buffer.
The data buffer, pointed to by AP.recPtr, is typically a struct variable
defined as the DBF record itself is defined. For example, if the DBF record
has 2 fields, LNAME and FNAME, each 25 characters, then then variable would be
typed as:
struct rectype {
CHAR tag; /* The Xbase DBF delete tag (must be included) */
CHAR lastName[25]; /* same length as first field's descriptor fieldLen */
CHAR firstName[25]; /* same length as second field's descriptor fieldLen */
}; /* 51 */
struct rectype recbuff;
The first record is at AP.recNo=1. The last is SDP.records, determined by
STAT_DATA_XB. The buffer must be at least as large as the record length
(SDP.recordLength).
This method of accessing the data file does not use any indexing. Generally,
this access method is not used except for special purposes (sequential
processing where order is not required). The preferred method to access the
data is by one of the keyed GET_XB routines.
If non-standard fields are used (i.e., non-char structure members to match
non-ASCII data fields in your non-standard DBF), then be aware that your
compiler more than likely will add padding to align on member-size boundaries.
This will result in a mis-match between your compiler structure (rectype
above) and your DBF structure (as described in fieldList[]). To prevent this,
place #pragma pack(1) / #pragma pack() around your structures that BULLET
uses. Consult your particular compiler for alternate methods if it does not
support #pragma pack.
ΓòÉΓòÉΓòÉ 9.31. ADD_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr
Append the data record in the data buffer to the end of the DBF file.
This method of adding a record involves no indexing. It is typically used to
build a data file en masse. Indexing is deferred until all records have been
added, and then quickly indexed using REINDEX_XB.
Since ADD_RECORD_XB is extremely fast, if you have several thousand data
records to be added at once, appending records with this routine and
reindexing when all have been added using REINDEX_XB is often faster than
using INSERT_XB for each record to add.
The record number assigned to the record appended is determined by BULLET, and
that record number is returned in AP.recNo.
If non-standard fields are used (i.e., non-char structure members to match
non-ASCII data fields in your non-standard DBF), then be aware that your
compiler more than likely will adding padding to align on member-size
boundaries. This will result in a mis-match between your compiler structure
and your DBF structure (as described in fieldList[]). To prevent this, place
#pragma pack(1) / #pragma pack() around your structures that BULLET uses.
Consult your particular compiler for alternate methods if it does not support
#pragma pack.
ΓòÉΓòÉΓòÉ 9.32. UPDATE_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.recNo
AP.recPtr
Write the updated data record to the physical record number.
This method of updating a data record must not be used if any field being used
as a key field (i.e., part of the key expression) is changed.
This method of updating a record is very fast if you know that that update is
not going to alter any field used as a key in any index file that uses it. You
must, of course, first get the data record into the record buffer. You can
then change it, and write the update out to disk using this routine.
If you need to change a field that is used as a key field, or part of one
(e.g., SUBSTR()), use the UPDATE_XB routine.
If you plan on reindexing with REINDEX_XB immediately after using this
routine, you may elect to update the data file using this method even if
changing any field used as a key, rather than UPDATE_XB. This since UPDATE_XB
is very disk intensive. However, if transaction support is needed (i.e.,
updates are dependent on other updates), then UPDATE_XB should be used.
ΓòÉΓòÉΓòÉ 9.33. DELETE_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.recNo
Tag the record at the physical record number as being deleted.
This does not tag any in-memory copies of the record so be sure to mark any
such copies as being deleted yourself.
The first byte of every DBF record is reserved for the tag field. This tag is
a space (ASCII 32) if the record is normal, or a * (ASCII 42) if it's marked
as being deleted. This delete tag is a reserved field in the DBF record and
as such is not defined as a formal field with a descriptor. Make sure that
you define your in-memory buffers to reserve the first byte for the delete
tag.
The Xbase DBF standard doesn't physically remove records marked as deleted
from the data file. It doesn't mark them as available/reusable either. To
physically remove records marked as deleted use PACK_RECORDS_XB
Records can be temporarily marked as deleted during processing and then
recalled to normal status when completed, useful for flagging a record as
having been processed (for example, mass updating using UPDATE_XB). The
GET_XB routines return the record number associated with a key (in AP.recNo),
and that record number can be used for this routine.
While the DELETE_RECORD_XB and UNDELETE_RECORD_XB routines provided in BULLET
use the * and SPACE characters only, you can use whatever character you want
in the tag field when you fill your record buffer structure's data. Normally,
you set the tag field to SPACE (x.tag = ' ';), but, for example, if you want
to implement your own, program-level locking you can use the tag field as a
marker to indicate the record is locked (by using an 'L' character, or ID with
bit7=1, or whatever you can think of) and use the very fast UPDATE_RECORD_XB
to set it. Another possibility is set to aside a field to be used as this,
say, along with the user ID of the lock owner.
The SKIP_TAG_SELECT item in SET_SYSVARS_XB can be set to have the REINDEX_XB
routine not place a key value into the index file if a record has a matching
tag field. This may be useful if you want to, say, generate an ad hoc index
for only undeleted records.
ΓòÉΓòÉΓòÉ 9.34. UNDELETE_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.recNo
Tag the record at the physical record number as being normal (not deleted).
This does not tag any in-memory copies of the record so be sure to mark any
such copies as being normal.
This routine removes the * character, as put there by DELETE_RECORD_XB, in the
tag field and replaces it with a SPACE. The tag field is always overwritten
with a SPACE, regardless of what it was.
ΓòÉΓòÉΓòÉ 9.35. DEBUMP_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.recNo
Remove the record identified by AP.recNo from the data file if and only if the
record is the last in the file. The file is automatically flushed before
debumping.
Unlike DELETE_RECORD_XB, this routine physically removes a data record from
the DBF file, provided that the record to delete is the last. STAT_DATA_XB can
be used to identify the last record number (SDP.records is the last). This,
when used after deleting any and all keys in all index files referencing this
record (see DELETE_KEY_XB), is useful if you are managing a transaction log
and need to back out changes made, beyond what BULLET performs.
If the record is not the last, alternate methods must be used. The simplest,
and often equally as good as physically deleting the record, is to just mark
the record as deleted using DELETE_RECORD_XB and let it remain in the file
until the next PACK_RECORDS_XB. Another option is to overwrite the record's
data with SPACES, or other appropriate field data (such as HIGH-VALUES, and
use UPDATE_XB), if necessary. This routine is the only method available to
physically remove a record from the file, short of using PACK_RECORDS_XB.
Removing a record with active keys referencing that record will result in an
access error (ERR_UNEXPECTED_EOF) when accessing that key with GET_XB
routines, or will generate stale results. Remove any keys that reference this
record before deleting it.
ΓòÉΓòÉΓòÉ 9.36. PACK_RECORDS_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
Rebuild the open DBF file by physically removing all records marked as
deleted.
Packing occurs in place using the existing file. It is recommended that you
use BACKUP_FILE_XB to copy the current DBF file before using this routine in
case of a failure during the pack process.
The newly packed file is truncated to reflect the current, actual size. All
records with the tag field set to * are removed from the file.
If there are index files for this DBF file, they must be reindexed after the
pack process by using REINDEX_XB.
Memo files are not affected by this routine. Before packing, it is
recommended that you traverse the data file to be packed, and for records that
are to be deleted, check to see if there is a memo record. If there is, delete
the memo. Do this for each such occurrence. This way, orphaned memo records
will not take up permanent space in the memo file.
ΓòÉΓòÉΓòÉ 9.37. GET_MEMO_SIZE_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle MDP.memoBytes
MDP.memoNo
Get the number of bytes used by the memo at MDP.memoNo.
Memo file allocation is made in blocks, typically of 512 bytes each.
Therefore, a memo of 10 bytes uses 1 allocation block, as would a 500-byte
memo. This size is stored with each memo record, and can be retrieved.
Before accessing a memo record, it's a good idea to retrieve the current size
of the memo so you know how large a buffer you may need if you intend to read
it all in, at one time, or even to just know how much to read, in total,
reading parts of it at a time.
The first memo is at MDP.memoNo=1. The last memo number cannot be easily
determined, but generally this does not need to be known. The memo number
identifying the memo's location is stored in the memo field area of the DBF
record. It is stored as a text string (e.g., "0000000001"). This is not a C
string; there is no zero terminator so sprintf() should be used. This number
is the physical block number at which the memo starts. Memos are always
stored in consecutive blocks, if more than a single block is needed. For
example, a memo of 513 bytes uses two blocks, say, #1 and #2. The next memo
added would use memo #3 (if #3 is available), rather than #2 since #2 was used
by the first memo. Memo numbers may be reassigned (see UPDATE_MEMO_XB). The
highest possible memo number is 589,822 (0x8FFFE). With the standard 512-byte
block size, this allows a memo file to be up to 288MB. If more memo data
space is needed, use a larger block size (e.g., 2KB block size allows over 1GB
per memo file).
Notice For All Memo Routines
In multitasking environments you should have a full-lock on the DBF file that
owns this memo file, or at least a record lock on the record that owns the
memo number. In BULLET, locking is not performed on the memo file. Instead,
the lock is implied when the lock is made on the DBF file. This because a
memo file is for one DBF file alone, and so if you have a lock on the DBF
before accessing the memo file (for whatever reason), then no other process
may lock the DBF and also access the memo.
This works only if you restrict your access to the memo file if you have a
lock on the DBF master file (the DBF that this DBT memo file belongs to) or on
the DBF record. For this routine, which only requires access to this memo
record, a record lock is sufficient since no writing is performed. Further, a
shared lock is all that is required. This because all that is required to
keep from stepping on other process's toes is that it be known that the
current memo header info (for this memo record), as known to this process, is
the current state of this memo. In other words, it must be true that the memo
file state on disk exactly matches the memo file state in memory. With a lock
in place, no other process may gain write access to change this memo, "out
from under you". A shared lock does allow the other process to read this
memo, and that may be used if no writing is needed.
Each memo routine following states its lock requirements (exclusive full lock,
shared full lock, exclusive record lock, or shared record lock).
ΓòÉΓòÉΓòÉ 9.38. GET_MEMO_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle *MDP.memoPtr
MDP.memoNo MDP.memoBytes
MDP.memoPtr
MDP.memoOffset
MDP.memoBytes
Read the specified number of bytes of the memo, starting at the offset, into
the buffer. The actual number of bytes read is returned.
Use GET_MEMO_SIZE_XB to determine that total number of bytes you may need to
read. With that, you can allocate a buffer of that size to read the entire
memo into. Or, you can read chunks of the memo, a chunk at a time, up to the
number of bytes in the memo.
The number of bytes actually read (and stored starting at MDP.memoPtr) is
returned in MDP.memoBytes (overwriting the value you placed there). If the
number of bytes requested is not the same as the number of bytes returned, you
attempted to read beyond the end of the memo. BULLET does not return an error
if you try this, which is SOP for file reads, so check the two if you need to
verify this. An error is returned, however, if you attempt to read at a
starting offset beyond the end of the actual memo data (i.e., MDP.memoOffset >
memo's data size). The first byte of the memo data is at .memoOffset=0.
It's recommended that a lock be in force on either the DBF (full-lock) or on
the record that this memo belongs to. A shared lock is okay since no writing
is done.
ΓòÉΓòÉΓòÉ 9.39. ADD_MEMO_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle MDP.memoNo
MDP.memoPtr
MDP.memoBytes
Add the data from the buffer for the specified bytes to a new memo. The memo
number used is returned.
Any data can be stored in a memo. The memo number returned can be any value;
it can even be less than the previous add's memo number. The reason for this
is that an avail-list is kept for the memo file, and any deleted or otherwise
freed blocks become available for re-use. The memo is stored in the first
contiguous group of free blocks large enough to satisfy the request. For
example, if MDP.memoBytes is from 1 to (blockSize-8) bytes, the first
available block is used. If the size needed is greater than 1 block, then the
avail-list is walked and the first contiguous group large enough to satisfy
the request is used. If none of the avail-list groups is large enough,
ultimately, the new memo data is appended to the end of the file. This is
also done if there are no avail-list items at all, such as in a memo file that
has never had deletes or updates.
The returned memo is a binary block number (ULONG). This value should be
converted into an ASCII string (sprintf can be used) and stored in the DBF
data record, in the memo field. The string should be of the form,
"0000000001" (for MDP.memoNo=1), with leading zeros, but no zero terminator
(exactly 10 bytes in size). This data record should then be written to disk
using UPDATE_RECORD_XB. Since BULLET can be used in non-standard Xbase mode,
where binary field values can be used, you can omit the conversion from binary
to ASCII if a standard DBF is not required. Likewise, when accessing a memo,
the conversion of the memo block number from ASCII to binary would not be
required.
It's recommended that a lock be in force on the DBF (full-lock). A shared
lock may not be used since writing to the memo file, and the DBF record, is
required. A full lock is required since the memo file header is read and
written.
ΓòÉΓòÉΓòÉ 9.40. UPDATE_MEMO_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle MDP.memoNo
MDP.memoNo
MDP.memoPtr
MDP.memoOffset
MDP.memoBytes
Update an existing memo. The update can overwrite current data, append new
data extending the current size, or it can shrink the current size.
Appending data so that the memo is extended may result in a new memo number
returned. The original memo blocks are made available for reuse (deleted).
Shrinking will not change the memo number, but unused blocks from the shrink
are made available for reuse.
If you want to change anything in the memo at MDP.memoNo, locate its position
within the memo with MDP.memoOffset and set the size in MDP.memoBytes. The
first data byte of a memo is located at MDP.offset=0. There are 8 bytes of
overhead per memo record (any number of blocks still has only the 8 bytes of
overhead), but these are transparent to any memo access you do. The bytes at
MDP.memoPtr overwrite the current memo data at the position specified. For
example, if you want to change the first 5 bytes of the first memo, set
MDP.memoNo=1, MDP.memoPtr=yourNewData, MDP.memoOffset=0, and MDP.memoBytes=5.
On return, MDP.memoNo is going to be the same as it was before the update,
since you are not extending the memo size in this example. Nothing further
needs to be done; the memo is updated.
If you want to add new memo data to an existing memo at MDP.memoNo, such as
adding another line item, or problem report paragraph, etc., set
MDP.memoOffset=theCurrentMemoSize (this locates to the end of the current memo
data), MDP.memoBytes=bytesYouWantToAppend, and MDP.memoPtr=yourDataToAppend.
If the old data size plus your newly added data still fits inside the last
memo block previously used, MDP.memoNo is returned the same as it was on
entry. However, if the new data requires that more blocks be allocated, the
entire memo is relocated to the next contiguous block group that is large
enough to store the data. That new block number is returned in MDP.memoNo,
and the old block number and all its blocks are placed on the top of the
avail-list.
If you want to shrink the size as reported by GET_MEMO_SIZE_XB from an
existing memo at MDP.memoNo, set MDP.memoBytes=newSizeYouWant, and
MDP.memoPtr=NULL. This means that you should have, before making this shrink
call, updated the memo data that occurs within this new size to be the data
size you want to be in the memo. For example, if you have 10 line items, say,
each 60 bytes long, and want to remove line item #5, you could do it by
reading all 10 line items to memory, moving line items #6 to 10 down one (so
they are now line items #5 to 9, effectively removing old line item #5), and
update the memo (by using memoOffset=0 and memoBytes=9*60). After this,
though, you still have 10*60 bytes as the memo size (old line item #10 is now
at #9 and still at #10). Since you want the size to reflect the real data in
the memo, set MDP.memoBytes=90, MDP.memoPtr=NULL, and update this memo number.
Only the memo's size is affected by this particular update. The size
specified must be smaller than the original size, or an error is returned.
It's recommended that a lock be in force on the DBF (full-lock). A record
lock should not be used if the update may result in blocks being moved, or the
memo being shrunk by a full block or more. A shared lock may not be used
since writing to the memo file, and to the DBF record if MDP.memoNo is new, is
required.
ΓòÉΓòÉΓòÉ 9.41. DELETE_MEMO_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle
MDP.memoNo
The memo and all its blocks are made available for reuse.
Before using PACK_RECORDS_XB, you should run through all DBF records and check
for those records that are deleted (record.tag='*') to be sure that any memo
belong to those records are deleted from the memo file. If this is not done,
orphaned memo records -- those that do not have a DBF record memo field
pointing to it, may be left in the memo file (forever!).
After deleting a memo record, update the DBF record's memo field by writing
<SPACES> (ASCII 32) to the memo field member. Update this to disk with
UPDATE_RECORD_XB as soon as possible (and before unlocking). A memo field
with no current memo record is indicated by spaces ("0000000000" should not be
used).
It's recommended that a lock be in force on the DBF (full-lock). Neither a
record lock nor a shared lock may be used since writing to the memo file
header and the DBF record is required.
ΓòÉΓòÉΓòÉ 9.42. MEMO_BYPASS_XB ΓòÉΓòÉΓòÉ
Pack: MEMODATAPACK Source Example
IN OUT
MDP.func MDP.stat
MDP.dbfHandle
MDP.memoBypass
Memo files are created, opened, closed, and flushed/reloaded by their
corresponding DBF data file action. To perform these tasks asynchronously,
this routine is used. Bypass routines are:
MDP.memoBypass Value
BYPASS_CREATE_MEMO 1
BYPASS_OPEN_MEMO 2
BYPASS_CLOSE_MEMO 3
BYPASS_READ_MEMO_HEADER 4
BYPASS_FLUSH_MEMO_HEADER 5
All bypass routines require the handle of the DBF file that this memo is for.
Nothing is returned here, except the result code. The memo handle from the
open is stored internally, but is available by using STAT_DATA_XB and checking
SDP.memoHandle. However, none of the BULLET memo routines use the memo handle
directly; all access to the memo file is through the master DBF file handle.
No data is required for input other than the DBF handle and memo bypass
routine to perform (see table above). All required info is obtained from the
DBF file's information. You may use an alternate block size, as set via
SET_SYSVARS_XB.
Generally, there is no need to call these routines using this bypass. However,
if you need to create a memo file anew (say, after the initial DBF was
created), and then open it, using these routines is the easiest way to
proceed.
Note: When creating a memo via the bypass method, the file ID is altered to
indicate that the DBF has a DBT memo file. The file ID is the first byte of
the DBF file. The ID is changed by OR'ing 0x88h with the current file ID
value. The next flush or close updates the disk image of the DBF with the new
file ID. The next DBF open, then, also opens the DBT memo file created here.
Be sure to always keep the DBT and DBF pairs in the same directory, if moved.
Since the DBF file is already open (and must be to use any of these routines),
you must use the open bypass routine to open the memo if you plan on using it.
Either that, or close the DBF after you've create the memo file, and simply
re-open the DBF, which also, now, opens the DBT memo file.
The other available bypass routines: close, read, and flush, typically will
not be used from this bypass routine. These operations are done automatically
when their corresponding DBF action is performed, and have little
functionality used on their own.
Before using BYPASS_READ_MEMO_HEADER or BYPASS_FLUSH_MEMO_HEADER, it's
recommended that a lock be in force on the DBF (full-lock). A shared lock can
be used for BYPASS_READ_MEMO_HEADER, but it must be a full lock.
ΓòÉΓòÉΓòÉ 9.43. FIRST_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Retrieve the first key in index order from the index file.
This routine does not access the DBF file and so does not retrieve the data
record. What it does do is locate the first logical key of the index file,
returning it, and also returning the record number within the DBF that the key
indexes.
To retrieve the data record you can use the GET_RECORD_XB routine. The
preferred method, however, is to use GET_FIRST_XB, which combines these
operations.
The key returned includes an enumerator if the index file allows duplicate
keys.
This routine is typically used to position the index file to the first key so
as to allow forward in-order access to the keys by using NEXT_KEY_XB.
If an external data file was specified in CREATE_INDEX_XB, the record number
returned by this routine does not refer to a DBF record, but rather is the
value supplied when the key was stored. This permits index access to your
data files (data files which are not maintained by BULLET, but by you).
ΓòÉΓòÉΓòÉ 9.44. EQUAL_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr
Search for the exact key in the index file.
This routine does not access the DBF file and so does not retrieve the data
record. What it does do is search for the key in the index, and if found,
returns the record number within the DBF that the key indexes. The key must
be an exact match, including the enumerator word if the index file is using
non-unique keys.
To retrieve the data record you can use the GET_RECORD_XB routine. The
preferred method, however, is to use GET_EQUAL_XB, which combines these
operations.
This routine returns no key in *keyPtr since, by definition, you already have
the key in the key buffer if this routine succeeds.
This routine finds only an exact match to the specified key (including the
enumerator if applicable).
ΓòÉΓòÉΓòÉ 9.45. EQUAL_OR_GREATER_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Search for the exact key in the index file and, if not found, get the key that
would have followed it.
This routine is similar to EQUAL_KEY_XB except that this routine returns a key
in *keyPtr (either the same as on entry, or if that is not found, then the
next greater key).
The main benefit of this routine is that it is an atomic operation. It differs
from setting the atomic mode flag of SET_SYSVARS_XB in that this routine
allows a fuzzy starting point: the key in AP.keyPtr, on entry (IN), need not
exist.
ΓòÉΓòÉΓòÉ 9.46. EQUAL_OR_LESSER_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Search for the exact key in the index file and, if not found, get the key that
would have come before it.
This routine is similar to EQUAL_KEY_XB except that this routine returns a key
in *keyPtr (either the same as on entry, or if that is not found, then the
previous, lesser key).
The main benefit of this routine is that it is an atomic operation. It differs
from setting the atomic mode flag of SET_SYSVARS_XB in that this routine
allows a fuzzy starting point: the key in AP.keyPtr, on entry (IN), need not
exist.
ΓòÉΓòÉΓòÉ 9.47. NEXT_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Retrieve the next key in index order from the index file.
This routine does not access the DBF file and so does not retrieve the data
record. What it does do is retrieve the next key of the index, returning it,
and also returning the record number within the DBF that the key indexes.
To retrieve the data record you can use the GET_RECORD_XB routine. The
preferred method, however, is to use GET_NEXT_XB, which combines these
operations.
The key returned includes an enumerator if the index file allows duplicates.
This routine is typically called after the index file has first been
positioned to a known key using either FIRST_KEY_XB or EQUAL_KEY_XB, or after
a previous NEXT_KEY_XB or even PREV_KEY_XB. What it basically does is get the
key following the current key, and then makes that key the new current key. ________________________________________________
If bit0 of the atomic mode flag of SET_SYSVARS_XB is set to 1, key access is
based on a given starting point. This simplifies index access in
multi-threaded code, where another thread may have altered the last key
accessed in the index file. This mode lets you set a starting point for the
operation by supplying in AP.keyPtr the key value to start at.
For example, say you use GET_FIRST_XB. On return, AP.keyPtr has the the very
first key. Say elsewhere in your multi-threaded program, another operation
accesses that same index file handle, and performs some other access, where
the last accessed key is no longer the same (i.e., not the first key). Your
first thread is expecting that a GET_NEXT_XB would get the second key,
however, it very likely won't since the second thread has altered the last
accessed key for that file handle. By using the atomic mode for key access,
your first thread, which has the first key value in its AP.keyPtr, can do a
call to GET_NEXT_XB and get expected results, since the NEXT operation first
positions to the value in AP.keyPtr and then follows up with a GET_NEXT
operation. This is performed within the Bullet kernel, and so won't be
interrupted by another thread (i.e., it is an atomic operation). For this to
work, you must ensure that the AP.keyPtr value is set to the value of the last
accessed key. This will always be the case unless uninitialized, or you are
using global variables for your threads' AP (AccessPack). On return from the
operation, AP.keyPtr will once again be set up for another atomic operation.
Note: You must supply a valid key value for this atomic access mode.
AP.keyPtr must be at least as large as the key length in all cases, and is to
have the starting point for the operation (i.e., the last accessed key). You
may, alternatively, set the first byte of the key buffer to 0 (but not
AP.keyPtr itself to NULL). This disables atomic mode for that access, and
reverts to the internally-stored last key accessed as the starting point.
ΓòÉΓòÉΓòÉ 9.48. PREV_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Retrieve the previous key in index order from the index file.
This routine does not access the DBF file and so does not retrieve the data
record. What it does do is retrieve the previous key of the index, returning
it and also returning the record number within the DBF that the key indexes.
To retrieve the data record you can use the GET_RECORD_XB routine. The
preferred method, however, is to use GET_PREV_XB, which combines these
operations.
The key returned includes an enumerator if the index file allows duplicates.
This routine is typically called after the index file has first been
positioned to a known key using either LAST_KEY_XB or EQUAL_KEY_XB, or after a
previous PREV_KEY_XB or even NEXT_KEY_XB. What it basically does is to get
the key previous the current key, and then make that key the new current key. ________________________________________________
If bit0 of the atomic mode flag of SET_SYSVARS_XB is set to 1, key access is
based on a given starting point. This simplifies index access in
multi-threaded code, where another thread may have altered the last key
accessed in the index file. This mode lets you set a starting point for the
operation by supplying in AP.keyPtr the key value to start at.
For example, say you use GET_FIRST_XB. On return, AP.keyPtr has the the very
first key. Say elsewhere in your multi-threaded program, another operation
accesses that same index file handle, and performs some other access, where
the last accessed key is no longer the same (i.e., not the first key). Your
first thread is expecting that a GET_NEXT_XB would get the second key,
however, it very likely won't since the second thread has altered the last
accessed key for that file handle. By using the atomic mode for key access,
your first thread, which has the first key value in its AP.keyPtr, can do a
call to GET_NEXT_XB and get expected results, since the NEXT operation first
positions to the value in AP.keyPtr and then follows up with a GET_NEXT
operation. This is performed within the Bullet kernel, and so won't be
interrupted by another thread (i.e., it is an atomic operation). For this to
work, you must ensure that the AP.keyPtr value is set to the value of the last
accessed key. This will always be the case unless uninitialized, or you are
using global variables for your threads' AP (AccessPack). On return from the
operation, AP.keyPtr will once again be set up for another atomic operation.
Note: You must supply a valid key value for this atomic access mode.
AP.keyPtr must be at least as large as the key length in all cases, and is to
have the starting point for the operation (i.e., the last accessed key). You
may, alternatively, set the first byte of the key buffer to 0 (but not
AP.keyPtr itself to NULL). This disables atomic mode for that access, and
reverts to the internally-stored last key accessed as the starting point.
ΓòÉΓòÉΓòÉ 9.49. LAST_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Retrieve the last key in index order from the index file.
This routine does not access the DBF file and so does not retrieve the data
record. What it does do is locate the last key of the index, returning it,
and also returning the record number within the DBF that the key indexes.
To retrieve the data record you can use the GET_RECORD_XB routine. The
preferred method, however, is to use GET_LAST_XB, which combines these
operations.
The key returned includes an enumerator if the index file allows duplicates.
This routine is typically used to position the index file to the last key so
as to allow reverse in-order access to the keys by using PREV_KEY_XB.
ΓòÉΓòÉΓòÉ 9.50. STORE_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.recNo
AP.keyPtr
Insert the key into the index file in proper key order.
This routine does not add the data record to the DBF file. It only inserts
the key and record number into the index file. Use INSERT_XB instead.
To do a complete data record and key insert, use ADD_RECORD_XB to add the data
record to the DBF, BUILD_KEY_XB to construct the key, then STORE_KEY_XB to
insert the key and record number information into the index file. If that key
already exists and the file allows duplicate keys, attach the proper
enumerator word and retry STORE_KEY_XB. INSERT_XB does this automatically.
ΓòÉΓòÉΓòÉ 9.51. DELETE_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle
AP.keyPtr
Physically remove the specified key from the index file.
This routine requires an exact key match for all bytes of the key, including
the enumerator word if duplicate keys are allowed.
Typically, this routine would seldom be used since deleted DBF data records
are only physically deleted during a PACK_RECORDS_XB operation, after which a
REINDEX_XB is done. It is useful if you are managing a transaction log and
need to back out changes made, beyond what BULLET performs. Also see
DEBUMP_RECORD_XB. If you have non-unique keys (where DUPS_ALLOWED is true),
you may have several keys that match your criterion, and only differ in their
enumerator. To identify which key, then, goes to a particular DBF record,
compare that key's AP.recNo with the number of your DBF record. If they are
the same, then this key belongs to that record. Use either the KEY_XB or the
GET_XB routines, then, before using this routine. In other words, use this
routine only after you have identified exactly the key to delete, and for the
exact data record. Once you have the record number, you can locate its key by
using GET_KEY_FOR_RECORD_XB.
ΓòÉΓòÉΓòÉ 9.52. BUILD_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle *AP.keyPtr
AP.recPtr
AP.keyPtr
Build the key for the specified data record based on the key expression for
the index file. If the index file allows duplicate keys, a 0-value enumerator
is attached.
This routine, like most of the mid-level routines, typically would not be used
since the high-level access routines take care of this detail automatically.
If used, it normally would be used prior to STORE_KEY_XB.
This routine can be replaced. See Custom Build-Key Routine.
Note: If DUPS_ALLOWED, this routine always sets the enumerator to \0\0.
Enumerator management, which is used to guarantee a unique key, is performed
only when the INSERT_XB routine is used.
ΓòÉΓòÉΓòÉ 9.53. GET_CURRENT_KEY_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
Retrieve the current key value for the specified index file handle and also
the data record number that it indexes. The key value includes the enumerator
if applicable.
This routine is useful in that it retrieves, on demand, the actual key value
of the last accessed key in the index file (and the data record number
associated with that key). STAT_INDEX_XB returns this information, too.
ΓòÉΓòÉΓòÉ 9.54. GET_KEY_FOR_RECORD_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle *AP.keyPtr
AP.recNo
AP.recPtr
AP.keyPtr
Retrieve the key for the record/record number pair.
This routine would typically be used prior to using DELETE_KEY_XB and
DEBUMP_RECORD_XB. The key returned includes the enumerator if applicable.
This routine sifts through any duplicate keys (if DUPS_ALLOWED) for the key
that matches the record/record number pair, and so requires both the actual
data record along with its physical record number (even if dups are not
allowed).
Typically this routine is extraneous; the key is available with a GET_XB
routine and so can be deleted from the information provided through normal
access.
This routine builds a key based on the supplied record at AP.recPtr and
searches the index for that key proper. If found, and if DUPS_ALLOWED, each
key matching the key proper has its record number compared to the record
number in AP.recNo. If that matches, too, then that is the exact key being
sought.
ΓòÉΓòÉΓòÉ 9.55. GET_FIRST_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Retrieve the first indexed key and its data record.
The key returned includes an enumerator if the index file uses non-unique keys
(DUPS_ALLOWED).
This routine is typically used to process a database in index order starting
at the first ordered key (and its data record). After processing this first
entry, subsequent in-order access of the database is achieved by using
GET_NEXT_XB, until the end of the database is reached, at which point an error
is returned.
This routine, like all the high-level GET_XB routines, fills in the AP.recNo
of the record accessed. In this case, it fills AP.recNo with the record
number pointed to by the first key. Since this is done upon each GET_XB
access, the AP pack is primed for an UPDATE_XB
ΓòÉΓòÉΓòÉ 9.56. GET_EQUAL_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr
Search for the exact key in the index file and return its data record.
This routine finds only an exact match to the specified key (including the
enumerator if applicable).
This routine, like all the high-level GET_XB routines, fills in the AP.recNo
of the record accessed. In this case, it fills AP.recNo with the record
number pointed to by the matching key. Since this is done upon each GET_XB
access, the AP pack is primed for an UPDATE_XB
ΓòÉΓòÉΓòÉ 9.57. GET_EQUAL_OR_GREATER_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Search for the exact key in the index file and return its data record, or if
not found, get the key and record that would have followed it.
This routine is similar to GET_EQUAL_XB except that this routine returns a key
in *keyPtr (either the same as on entry, or if that is not found, then the
next greater key).
The main benefit of this routine is that it is an atomic operation. It differs
from setting the atomic mode flag of SET_SYSVARS_XB in that this routine
allows a fuzzy starting point: the key in AP.keyPtr, on entry (IN), need not
exist.
ΓòÉΓòÉΓòÉ 9.58. GET_EQUAL_OR_LESSER_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Search for the exact key in the index file and return its data record, or if
not found, get the key and record that would have come before it.
This routine is similar to GET_EQUAL_XB except that this routine returns a key
in *keyPtr (either the same as on entry, or if that is not found, then the
previous, lesser key).
The main benefit of this routine is that it is an atomic operation. It differs
from setting the atomic mode flag of SET_SYSVARS_XB in that this routine
allows a fuzzy starting point: the key in AP.keyPtr, on entry (IN), need not
exist.
ΓòÉΓòÉΓòÉ 9.59. GET_NEXT_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Retrieve the next indexed key and its data record.
The key returned includes an enumerator if the index file uses non-unique keys
(DUPS_ALLOWED).
This routine is typically called after the index file has first been
positioned to a known key using either GET_FIRST_XB or GET_EQUAL_XB, or after
a previous GET_NEXT_XB or even GET_PREV_XB. What it basically does is get the
key and data record following the current key, and then makes that key the new
current key.
This routine, like all the high-level GET_XB routines, fills in the AP.recNo
of the record accessed. In this case, it fills AP.recNo with the record
number pointed to by the next key (now the current key). Since this is done
upon each GET_XB access, the AP pack is primed for an UPDATE_XB. ________________________________________________
If bit0 of the atomic mode flag of SET_SYSVARS_XB is set to 1, key access is
based on a given starting point. This simplifies index access in
multi-threaded code, where another thread may have altered the last key
accessed in the index file. This mode lets you set a starting point for the
operation by supplying in AP.keyPtr the key value to start at.
For example, say you use GET_FIRST_XB. On return, AP.keyPtr has the the very
first key. Say elsewhere in your multi-threaded program, another operation
accesses that same index file handle, and performs some other access, where
the last accessed key is no longer the same (i.e., not the first key). Your
first thread is expecting that a GET_NEXT_XB would get the second key,
however, it very likely won't since the second thread has altered the last
accessed key for that file handle. By using the atomic mode for key access,
your first thread, which has the first key value in its AP.keyPtr, can do a
call to GET_NEXT_XB and get expected results, since the NEXT operation first
positions to the value in AP.keyPtr and then follows up with a GET_NEXT
operation. This is performed within the Bullet kernel, and so won't be
interrupted by another thread (i.e., it is an atomic operation). For this to
work, you must ensure that the AP.keyPtr value is set to the value of the last
accessed key. This will always be the case unless uninitialized, or you are
using global variables for your threads' AP (AccessPack). On return from the
operation, AP.keyPtr will once again be set up for another atomic operation.
Note: You must supply a valid key value for this atomic access mode.
AP.keyPtr must be at least as large as the key length in all cases, and is to
have the starting point for the operation (i.e., the last accessed key). You
may, alternatively, set the first byte of the key buffer to 0 (but not
AP.keyPtr itself to NULL). This disables atomic mode for that access, and
reverts to the internally-stored last key accessed as the starting point.
ΓòÉΓòÉΓòÉ 9.60. GET_PREV_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Retrieve the previous indexed key and its data record.
The key returned includes an enumerator if the index file uses non-unique keys
(DUPS_ALLOWED).
This routine is typically called after the index file has first been
positioned to a known key using either GET_LAST_XB or GET_EQUAL_XB, or after a
previous GET_PREV_XB or even GET_NEXT_XB. What it basically does is get the
key and data record preceding the current key, and then makes that key the new
current key.
This routine, like all the high-level GET_XB routines, fills in the AP.recNo
of the record accessed. In this case, it fills AP.recNo with the record
number pointed to by the previous key (now the current key). Since this is
done upon each GET_XB access, the AP pack is primed for an UPDATE_XB. ________________________________________________
If bit0 of the atomic mode flag of SET_SYSVARS_XB is set to 1, key access is
based on a given starting point. This simplifies index access in
multi-threaded code, where another thread may have altered the last key
accessed in the index file. This mode lets you set a starting point for the
operation by supplying in AP.keyPtr the key value to start at.
For example, say you use GET_FIRST_XB. On return, AP.keyPtr has the the very
first key. Say elsewhere in your multi-threaded program, another operation
accesses that same index file handle, and performs some other access, where
the last accessed key is no longer the same (i.e., not the first key). Your
first thread is expecting that a GET_NEXT_XB would get the second key,
however, it very likely won't since the second thread has altered the last
accessed key for that file handle. By using the atomic mode for key access,
your first thread, which has the first key value in its AP.keyPtr, can do a
call to GET_NEXT_XB and get expected results, since the NEXT operation first
positions to the value in AP.keyPtr and then follows up with a GET_NEXT
operation. This is performed within the Bullet kernel, and so won't be
interrupted by another thread (i.e., it is an atomic operation). For this to
work, you must ensure that the AP.keyPtr value is set to the value of the last
accessed key. This will always be the case unless uninitialized, or you are
using global variables for your threads' AP (AccessPack). On return from the
operation, AP.keyPtr will once again be set up for another atomic operation.
Note: You must supply a valid key value for this atomic access mode.
AP.keyPtr must be at least as large as the key length in all cases, and is to
have the starting point for the operation (i.e., the last accessed key). You
may, alternatively, set the first byte of the key buffer to 0 (but not
AP.keyPtr itself to NULL). This disables atomic mode for that access, and
reverts to the internally-stored last key accessed as the starting point.
ΓòÉΓòÉΓòÉ 9.61. GET_LAST_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recPtr *AP.recPtr
AP.keyPtr *AP.keyPtr
Retrieve the last indexed key and its data record.
The key returned includes an enumerator if the index file uses non-unique keys
(DUPS_ALLOWED).
This routine is typically used to process a database in reverse index order
starting at the last ordered key (and its data record). After processing this
last entry, subsequent reverse-order access of the database is achieved by
using GET_PREV_XB, until the top of the database is reached, at which point an
error is returned.
This routine, like all the high-level GET_XB routines, fills in the AP.recNo
of the record accessed. In this case, it fills AP.recNo with the record
number pointed to by the last key. Since this is done upon each GET_XB access,
the AP pack is primed for an UPDATE_XB
ΓòÉΓòÉΓòÉ 9.62. INSERT_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.recNo *AP.keyPtr
AP.recPtr
AP.keyPtr
AP.nextPtr
Append the data records to data files and build and insert the related keys
into all linked index files. (Alternate forms are possible.)
This routine is used to add new entries into a database. Up to 256 index
files may be inserted into per call, with up to 256 data files being added,
too, for a total of 512 files managed per single INSERT_XB call.
Note: Bullet comes in 100, 250, and 1024-file versions and so this routine is
able to use as many files as handles are still available. If non-standard
fields are used (i.e., non-char structure members to match non-ASCII data
fields in your non-standard DBF), then be aware that your compiler more than
likely will add padding to align on member-size boundaries. This will result
in a mis-match between your compiler structure and your DBF structure (as
described in fieldList[]). To prevent this, place #pragma pack(1) / #pragma
pack() around your structures that BULLET uses. Consult your particular
compiler for alternate methods if it does not support #pragma pack.
Only index handles are listed in AP.handle. Each index file has associated
with it a data file, known internally to BULLET (the xbLink from OPEN_XB).
There may be more than one index file for a data file, but there is always one
data file per index handle specified in the list. In other words, you can
list five index files, each indexing the same xbLink data file, and have
BULLET perform an atomic insert of that list. Or, another possibility is that
you have a single index file, indexing a single data file. Or, you can list
256 index files, each indexing a single data file (512 total files).
This and several other routines are transaction-list-based. This means that
if a failure occurs prior to the routine's completion, all changes made to the
database by the routine will be backed-out, and the database (data and index
files) effectively restored to its original state.
If the routine failed to complete, the function return value is the number
(1-based) of the pack that caused the failure. A positive number indicates
the failure was from an index operation; a negative number indicates the
failure was from a data operation. In each case, the absolute value of the
return code is the list item that failed (the pack index). For example, if
five index handles are in the list (AP[0] to AP[4]), and an error occurred on
the last pack's index file, the return code would be positive 5, indicating
the fifth pack (AP[4]) failed. Since it was a positive 5, the index file was
being processed when the error occurred. Being processed means not only
physical access, but verification, etc. If the return code was -5, then
again, the error was in the fifth pack, but since it is negative, the error
occurred while processing the data file. In either case, upon return, the
database is effectively restored to the way it was before the INSERT_XB call
was made. Remedy the error, if possible, and INSERT_XB again.
Each pack must include a separate key buffer. You must not share a common key
buffer. Doing so disables any chance of recovering the index files in case of
error, since it is in these buffers that BULLET places the newly built keys,
and it is from these that BULLET, upon an error condition, deletes the keys
(required for roll-back).
The enumerator is automatically set up by this routine, if required
(DUPS_ALLOWED and the key already exists with enumerator 0). It does this by
seeking the last possible enumerator value (0xFFFF) and then backing up to the
previous key. That key's enumerator is evaluated and incremented, and used as
this key's.
Specifying Files
As mentioned, only the index file handles are specified in AP.handle. Data
files are implicitly specified by their links to the index files, as specified
when the index file was opened (OP.xbLink). INSERT_XB can process up to 256
index files per call. Since each index file requires a data file, this means
that up to 256 data files can be processed per call, as well. Also possible
is that all 256 index handles refer to the same, single data file. Yet
another possibility is that there is 1 index file, and so 1 data file. The
possibilities can include those and anything in between.
Example: Specifying a single index file
The simplest form is where a single index handle is specified. This implies a
single data file, too. AccessPack setup for this is:
AP.func = INSERT_XB;
AP.handle = indexHandle;
AP.recNo = 0;
AP.recPtr = &recordStruct; // contents referred to below as *recordStruct
AP.keyPtr = keyBuffer;
AP.nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct is used as a new record that is appended to the
data file. The data file was linked to this index during the index open,
in OP.xbLink.
2. A key is built by BULLET, based on the data in *recordStruct, and that
key is inserted into the index file (AP.handle). Stored with the key is
the record number of the record added above.
Note: AP.recNo must be set to 0 prior to the call. Any positive number
results in an error (0x80000000, and negative numbers, may be used when more
than one AP pack is used - see below).
Upon return, if no error, the return code is 0. AP.recNo is set to the
physical record number in the data file that *recordStruct was placed. The
key that was stored, including any enumerator, is in *keyBuffer.
Upon return, and there was an error, the return code is either -1 or 1. If -1,
the error was caused during processing of the data file portion, and the error
code itself is in AP.stat. If +1, the error was caused during processing of
the index file, and the error code itself is in AP.stat, as well. The return
code is, as in all BULLET transaction-list routines, an index of the AP pack
that generated the error -- negative if a data file error, positive if an
index file error. Since this example has only the single pack, only a -1 or
+1 could be returned, or 0.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for a single data file
Two index files, related to the same data file, would set AccessPack to:
AP[0].func = INSERT_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = 0;
AP[0].recPtr = &recordStruct;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = 0x80000000;
AP[1].recPtr = &recordStruct;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct is used as a new record that is appended
(added) to the data file.
2. A key is built by BULLET, based on the data in *recordStruct, and that
key is inserted into the index file (AP[0].handle). Stored with the key
is the record number of the record added above.
3. A second key is built by BULLET, based on the data in *recordStruct, and
that key is inserted into the second index file (AP[1].handle). Stored
with the key is the record number of the record added above.
Note: The 0x80000000 in AP[1].recNo signifies that AP[1] is using the same
data record that was appended during processing of AP[0]. This results in
just the one data record being added. AP[1].recPtr must still, however, point
to the same data as AP[0].recPtr does.
Upon return, if no error, the return code is 0. AP[0].recNo is set to the
physical record number in the data file that *recordStruct was placed. The
key that was stored for the first index, including any enumerator, is in the
buffer at AP[0].keyPtr. AP[1].recNo is set to the same physical record number
as AP[0].recNo, except that the record number is negative: For example, if
AP[0].recNo is 22 on return, AP[1].recNo is -22 (the original 0x80000000 value
is overwritten). The key that was stored for the second index, including any
enumerator, is in the buffer at AP[1].keyPtr.
Upon return, and there was an error, the return code can be -2, -1, 1, or 2.
If negative, the error was caused during processing of that AP pack's data
file portion, and the error code itself is in AP[abs(rez)-1].stat (where rez
is the return code, and -1 since C arrays start at 0). If the return code was
positive, the error was caused during processing of that AP pack's index file,
and the error code itself is in AP[rez-1].stat, as well. The return code is,
as in all BULLET transaction-list routines, an index of the AP pack that
generated the error -- negative if a data file error, positive if an index
file error.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for each of two different data files
Four total files: two index files related to one data file, and two other
index files related to another data file, would set AccessPack to:
AP[0].func = INSERT_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = 0;
AP[0].recPtr = &recordStruct_0;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = 0x80000000;
AP[1].recPtr = &recordStruct_0;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = AP[2];
AP[2].handle = indexHandle_2;
AP[2].recNo = 0;
AP[2].recPtr = &recordStruct_1;
AP[2].keyPtr = keyBuffer_2;
AP[2].nextPtr = AP[3];
AP[3].handle = indexHandle_3;
AP[3].recNo = 0x80000000;
AP[3].recPtr = &recordStruct_1;
AP[3].keyPtr = keyBuffer_3;
AP[3].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct_0 is used as a new record that is appended to
the data file linked to the index file in AP[0].handle.
2. A key is built by BULLET, based on the data in *recordStruct_0, and that
key is inserted into the index file (AP[0].handle). Stored with the key
is the record number of the record added above, for _0.
3. A second key is built by BULLET, based on the data in *recordStruct_0,
and that key is inserted into the second index file (AP[1].handle).
Stored with the key is the record number of the record added above, using
*recordStruct_0.
4. The data in *recordStruct_1 is used as a new record that is appended to
the data file linked to the index file in AP[2].handle.
5. A third key is built by BULLET, based on the data in *recordStruct_1, and
that key is inserted into the index file (AP[2].handle). Stored with the
key is the record number of the record added above, for _1.
6. A fourth key is built by BULLET, based on the data in *recordStruct_1,
and that key is inserted into the fourth index file (AP[3].handle).
Stored with the key is the record number of the record added above, using
*recordStruct_1.
Note: The 0x80000000 in AP[1].recNo signifies that AP[1] is using the same
data record that was appended during processing of AP[0]. This results in
just the one data record being added. AP[1].recPtr must still, however, point
to the same data as AP[0].recPtr does. The same applies to AP[2] and AP[3]
(though different values, of course).
Upon return, if no error, the return code is 0. AP[0].recNo is set to the
physical record number in the data file that *recordStruct_0 was placed. The
key that was stored for the first index, including any enumerator, is in the
buffer at AP[0].keyPtr. AP[1].recNo is set to the same physical record number
as AP[0].recNo, except that the record number is negative: For example, if
AP[0].recNo is 22 on return, AP[1].recNo is -22 (the original 0x80000000 value
is overwritten). The key that was stored for the second index, including any
enumerator, is in the buffer at AP[1].keyPtr. AP[2].recNo is set to the
physical record number in the data file that *recordStruct_1 was placed. The
key that was stored for the third index, including any enumerator, is in the
buffer at AP[2].keyPtr. AP[3].recNo is set to the same physical record number
as AP[2].recNo, except that the record number is negative: For example, if
AP[2].recNo is 74 on return, AP[3].recNo is -74 (the original 0x80000000 value
is overwritten). The key that was stored for the fourth index, including any
enumerator, is in the buffer at AP[3].keyPtr.
Upon return, and there was an error, the return code can be -4 to -1, or 1 to
4. If negative, the error was caused during processing of that AP pack's data
file portion, and the error code itself is in AP[abs(rez)-1].stat (where rez
is the return code, and -1 since C arrays start at 0). If the return code was
positive, the error was caused during processing of that AP pack's index file,
and the error code itself is in AP[rez-1].stat, as well. The return code is,
as in all BULLET transaction-list routines, an index of the AP pack that
generated the error -- negative if a data file error, positive if an index
file error.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for two records in the same data file
Three files: two index files related to one data file, where two data records
are to be appended, would set AccessPack to:
AP[0].func = INSERT_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = 0;
AP[0].recPtr = &recordStruct_0;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = 0x80000000;
AP[1].recPtr = &recordStruct_0;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = AP[2];
AP[2].handle = indexHandle_0;
AP[2].recNo = 0;
AP[2].recPtr = &recordStruct_1;
AP[2].keyPtr = keyBuffer_2;
AP[2].nextPtr = AP[3];
AP[3].handle = indexHandle_1;
AP[3].recNo = 0x80000000;
AP[3].recPtr = &recordStruct_1;
AP[3].keyPtr = keyBuffer_3;
AP[3].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct_0 is used as a new record that is appended to
the data file linked to the index file in AP[0].handle.
2. A key is built by BULLET, based on the data in *recordStruct_0, and that
key is inserted into the index file (AP[0].handle). Stored with the key
is the record number of the record added above, for _0.
3. A second key is built by BULLET, based on the data in *recordStruct_0,
and that key is inserted into the second index file (AP[1].handle).
Stored with the key is the record number of the record added above, using
*recordStruct_0.
4. The data in *recordStruct_1 is used as a new record that is appended to
the data file linked to the index file in AP[2].handle. Since
AP[2].handle is the same index file as that of AP[0].handle, this means
it's also the same data file as was just operated on above -- a second
data record is appended to the data file. The net effect of this
operation is to call INSERT_XB twice, once for one insert, then again for
the second. The difference is that the operation is atomic -- if one
fails, the other is not committed; it's an "all or nothing" operation.
5. A third key is built by BULLET, based on the data in *recordStruct_1, and
that key is inserted into the index file (AP[2].handle). Stored with the
key is the record number of the record added directly above, for _1. Note
that this index file is the same as specified in AP[0].handle.
6. A fourth key is built by BULLET, based on the data in *recordStruct_1,
and that key is inserted into the fourth index file (AP[3].handle).
Stored with the key is the record number of the record added above, using
*recordStruct_1.
The return ritual is as described above, for "Specifying two index files each
for two different data files".
Example: Specifying a single index file for a previously added data record
This form lets you insert a key without adding a data record. This would be
required if you were, for example, creating a temporary index of select
records in a data file (i.e., the data records already exist, you just want to
index them). AccessPack setup for this is:
AP.func = INSERT_XB;
AP.handle = indexHandle;
AP.recNo = -recordNumberOfExistingRecord;
AP.recPtr = &recordStruct;
AP.keyPtr = keyBuffer;
AP.nextPtr = NULL;
A call to BULLET with the above does the following:
1. A key is built by BULLET, based on the data in *recordStruct, and that
key is inserted into the index file (AP.handle). Stored with the key is
the absolute value of the record number specified in AP.recNo (which is
set to negative record number).
Upon return, if no error, the return code is 0. AP.recNo is changed to
abs(AP.recNo). The key that was stored, including any enumerator, is in
*keyBuffer. No data file access is made.
Upon return, and there was an error, the return code is either -1 or 1. If -1,
the error was caused during processing of the data file portion, and the error
code itself is in AP.stat. If +1, the error was caused during processing of
the index file, and the error code itself is in AP.stat, as well. The return
code is, as in all BULLET transaction-list routines, an index of the AP pack
that generated the error -- negative if a data file error, positive if an
index file error. Since this example has only the single pack, only a -1 or
+1 could be returned.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
An example use of this INSERT_XB feature is to create an ad hoc index of, say,
records marked as deleted. To do this, create a new index file (say, with a
key of NAME). Get each data record, by record number using GET_RECORD_XB (for
records 1 to number-of-records), and check the .tag byte. If '*', call
INSERT_XB with the negative value of AP.recNo. Do this for every such marked
record. After all records are processed, you have an index of all deleted
records in the data file. Delete the index when no longer needed. That's
just one example.
ΓòÉΓòÉΓòÉ 9.63. UPDATE_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle *AP.keyPtr
AP.recNo
AP.recPtr
AP.keyPtr
AP.nextPtr
Update any and all files in the transaction list if necessary, including both
index and data files.
This routine is used to update data records while also updating the index
files if a key field has changed due to data record updates. Up to 256 index
files may be updated per call, as well as 256 data files, too, for a total of
512 files managed per single UPDATE_XB call.
Only index handles are listed in AP.handle. Each index file has associated
with it a data file, known internally to BULLET (the xbLink from OPEN_XB).
There may be more than one index file for a data file, but there is always one
data file per index handle specified in the list. In other words, you can
list five index files, each indexing the same xbLink data file, and have
BULLET perform an atomic update of that list. Or, another possibility is that
you have a single index file, indexing a single data file. Or, you can list
256 index files, each indexing a single data file (512 total files).
This and several other routines are transaction-list-based. This means that
if a failure occurs prior to the routine's completion, all changes made to the
database by the routine will be backed-out, and the database (data and index
files) effectively restored to its original state.
If the routine failed to complete, the function return value is the number
(1-based) of the pack that caused the failure. A positive number indicates
the failure was from an index operation; a negative number indicates the
failure was from a data operation. In each case, the absolute value of the
return code is the list item that failed (the pack index). For example, if
five index handles are in the list(AP[0] to AP[4]), and an error occurred on
the last pack's index file, the return code would be positive 5, indicating
the fifth pack (AP[4]) failed. Since it was a positive 5, the index file was
being processed when the error occurred. Being processed means not only
physical access, but verification, etc. If the return code was -5, then
again, the error was in the fifth pack, but since it is negative, the error
occurred while processing the data file. In either case, upon return, the
database is restored to the way it was before the UPDATE_XB call was made.
Remedy the error, if possible, and UPDATE_XB again.
Each pack must include a separate key buffer. You must not share a common key
buffer. Doing so disables any chance of recovering the index files in case of
error, since it is in these buffers that BULLET places any newly built keys,
and it is from these that BULLET, upon an error condition, deletes these keys
(required for roll-back).
The enumerator is automatically maintained by this routine, if required
(DUPS_ALLOWED and the key already exists with enumerator 0). The process is
the same as INSERT_XB's.
How an update works
All data records specified in the list are read from disk into memory, except
those with AP.recNo=0. Therefore, a memory allocation large enough to store
all unique data records is made upon entry to this routine (and released at
exit). For example, if the list includes two implicit data files, and the
record lengths of those two data files are 2048 and 4096 bytes, an allocation
of 6K is made. In addition, 40KB more is allocated for workspace. So, for
this example, 46K is allocated (rounded up to 48KB, the next 4KB page
boundary). Since up to 256 unique records are possible, where a unique record
is identified by handle/record number, be aware of the memory requirements if
you are updating very large databases (e.g., 256 unique records, each 4KB in
length, would have UPDATE_XB allocate a bit over 1MB of memory for this call).
After the data records have been read from disk, each list-item is processed,
in order. The disk record image previously read is compared with the record
image at AP.recPtr. If the same, that item is skipped, and the next item in
the list is processed. If you know beforehand that that record is the same,
set that item's AP.recNo=0 so you can avoid having its disk image read and
stored (or do not include it in the list at all). If the images differ,
BULLET creates a key for the index file being processed, for each record image
(the original and the one in AP.recPtr). If the keys generated are the same,
no index file update is needed. If different, the original key for that
record is deleted from that index file, and the new key inserted. Finally, the
new record replaces the old, the new directly overwriting the original. Note
that the actual sequence of the update event differs somewhat from this
description in order to optimize the process.
Specifying Files
As mentioned, only the index file handles are specified in AP.handle. Data
files are implicitly specified by their links to the index files, as specified
when the index file was opened (OP.xbLink). UPDATE_XB can process up to 256
index files per call. Since each index file requires a data file, this means
that up to 256 data files can be processed per call as well. Also possible is
that all 256 index handles refer to the same, single data file. Yet another
possibility is that there is 1 index file, and so 1 data file. The
possibilities can include those and anything in between.
Example: Specifying a single index file
The simplest form is where a single index handle is specified. This implies a
single data file, too. AccessPack setup for this is:
AP.func = UPDATE_XB;
AP.handle = indexHandle;
AP.recNo = recordToUpdate;
AP.recPtr = &recordStruct;
AP.keyPtr = keyBuffer;
AP.nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct is used as the new record that is to replace
the data record at AP.recNo. The data file was linked to this index file
(AP.handle) during the index open, in OP.xbLink.
2. If the record data in *recordStruct is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
AP.recNo must be set to the record number that you are updating. Any GET_XB
routine (GET_EQUAL_XB, etc.) may be used to identify the number of a data
record. Key access has the obvious advantage of knowing the record number of
a specific key (for example, Betty Barbar's data). Any record number, from 1
to number of records in the data file, can be used. In addition, a negative
record number can be used. This is treated exactly the same as a positive
record number (the absolute value is used). The reason this is allowed is
because INSERT_XB replaces 0x80000000 record numbers with the negative value
of the previous insert.
Upon return, if no error, the return code is 0. If the record data was new,
the key for that data record, including any enumerator, is in *keyBuffer.
This is so even if key fields had not changed.
Upon return, and there was an error, the return code is either -1 or 1. If -1,
the error was caused during processing of the data file portion, and the error
code itself is in AP.stat. If +1, the error was caused during processing of
the index file, and the error code itself is in AP.stat, as well. The return
code is, as in all BULLET transaction-list routines, an index of the AP pack
that generated the error -- negative if a data file error, positive if an
index file error. Since this example has only the single pack, only a -1 or
+1 could be returned.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for a single data file
Two index files, related to the same data file, would set AccessPack to:
AP[0].func = UPDATE_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = recordToUpdate;
AP[0].recPtr = &recordStruct;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = recordToUpdate;
AP[1].recPtr = &recordStruct;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct is used as the new record that is to replace
the data record at AP.recNo. The data file was linked to this index file
(AP.handle) during the index open, in OP.xbLink.
2. If the record data in *recordStruct is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
3. The operation performed directly above is repeated, this time for the
second index file. The new record data, and the record number to update
are, for this particular example, the same.
AP.recNo must be set to the record number that you are updating. Each
AP[].recNo must be set to a valid record number, even if the record number is
the same as the previous AP[] pack's (the case where you have more than one
index file for a data file). BULLET knows if the record number duplicates a
number in a previous AP pack, and allocates resources for only the first
encounter of the data record. Subsequent encounters refer to the first.
Upon return, if no error, the return code is 0. If the new and original data
records differ, the key for the new data record, including any enumerator, is
in the buffer at AP[0].keyPtr. This even if the key fields did not change.
The same applies to the second index, with the new data key in AP[1].keyPtr.
Upon return, and there was an error, the return code can be -2, -1, 1, or 2.
If negative, the error was caused during processing of that AP pack's data
file portion, and the error code itself is in AP[abs(rez)-1].stat (where rez
is the return code, and -1 since C arrays start at 0). If the return code was
positive, the error was caused during processing of that AP pack's index file,
and the error code itself is in AP[rez-1].stat, as well. The return code is,
as in all BULLET transaction-list routines, an index of the AP pack that
generated the error -- negative if a data file error, positive if an index
file error.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for each of two different data files
Four total files: two index files related to one data file, and two other
index files related to another data file, would set AccessPack to:
AP[0].func = UPDATE_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = recordToUpdate_0;
AP[0].recPtr = &recordStruct_0;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = recordToUpdate_0;
AP[1].recPtr = &recordStruct_0;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = AP[2];
AP[2].handle = indexHandle_2;
AP[2].recNo = recordToUpdate_1;
AP[2].recPtr = &recordStruct_1;
AP[2].keyPtr = keyBuffer_2;
AP[2].nextPtr = AP[3];
AP[3].handle = indexHandle_3;
AP[3].recNo = recordToUpdate_1;
AP[3].recPtr = &recordStruct_1;
AP[3].keyPtr = keyBuffer_3;
AP[3].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct_0 is used as the new record that is to replace
the data record at AP[0].recNo in the data file linked to the index file
in AP[0].handle.
2. If the record data in *recordStruct_0 is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
3. The operation performed directly above is repeated, this time for the
second index file. The new record data, and the record number to update,
are for this particular example, the same.
4. The data in *recordStruct_1 is used as the new record that is to replace
the data record at AP[2].recNo in the data file linked to the index file
in AP[2].handle.
5. If the record data in *recordStruct_1 is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
6. The operation performed directly above is repeated, this time for the
fourth index file. The new record data, and the record number to update
are, for this particular example, the same.
Upon return, if no error, the return code is 0. If the new and original data
records differ, the keys for the new data records, including any enumerators,
are in the buffers at AP[0].keyPtr to AP[3].keyPtr. This even if the key
fields did not change. If one, or all, of the new data records matched the
original data record, nothing is placed in *keyBuffer for that index.
Upon return, and there was an error, the return code can be -4 to -1, or 1 to
4. If negative, the error was caused during processing of that AP pack's data
file portion, and the error code itself is in AP[abs(rez)-1].stat (where rez
is the return code, and rez-1 since C arrays start at 0). If the return code
was positive, the error was caused during processing of that AP pack's index
file, and the error code itself is in AP[rez-1].stat, as well. The return
code is, as in all BULLET transaction-list routines, an index of the AP pack
that generated the error -- negative if a data file error, positive if an
index file error.
Note: If an error occurred after any part of the database had changed (during
this particular call), then any and all changes that were made are backed-out,
and the files restored to the same state as before the call.
Example: Specifying two index files for two records in the same data file
Three files: two index files related to one data file, where two data records
are to be updated, would set AccessPack to:
AP[0].func = UPDATE_XB;
AP[0].handle = indexHandle_0;
AP[0].recNo = recordToUpdate_0;
AP[0].recPtr = &recordStruct_0;
AP[0].keyPtr = keyBuffer_0;
AP[0].nextPtr = AP[1];
AP[1].handle = indexHandle_1;
AP[1].recNo = recordToUpdate_0;
AP[1].recPtr = &recordStruct_0;
AP[1].keyPtr = keyBuffer_1;
AP[1].nextPtr = AP[2];
AP[2].handle = indexHandle_0;
AP[2].recNo = recordToUpdate_1;
AP[2].recPtr = &recordStruct_1;
AP[2].keyPtr = keyBuffer_2;
AP[2].nextPtr = AP[3];
AP[3].handle = indexHandle_1;
AP[3].recNo = recordToUpdate_1;
AP[3].recPtr = &recordStruct_1;
AP[3].keyPtr = keyBuffer_3;
AP[3].nextPtr = NULL;
A call to BULLET with the above does the following:
1. The data in *recordStruct_0 is used as the new record that is to replace
the data record at AP[0].recNo in the data file linked to the index file
in AP[0].handle.
2. If the record data in *recordStruct_0 is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
3. The operation performed directly above is repeated, this time for the
second index file. The new record data, and the record number to update,
are for this particular example, the same.
4. The data in *recordStruct_1 is used as the new record that is to replace
the data record at AP[2].recNo in the data file linked to the index file
in AP[2].handle. This is the same index file as the first AP pack, and
also the same data file. However, this is a different record number.
5. If the record data in *recordStruct_1 is the same as the original disk
record, nothing is done. If the data is new, the key fields are compared
to that belonging to the original disk record, and if the same, only the
record data is updated. If the new record's key differs from the
original's, the original key for this record is removed from the index,
and the new key inserted.
6. The operation performed directly above is repeated, this time for the
fourth index file. The new record data, and the record number to update
are, for this particular example, the same.
The return ritual is as described above, for "Specifying two index files each
for two different data files".
ΓòÉΓòÉΓòÉ 9.64. REINDEX_XB ΓòÉΓòÉΓòÉ
Pack: ACCESSPACK Source Example
IN OUT
AP.func AP.stat
AP.handle AP.recNo
AP.keyPtr *AP.keyPtr
AP.nextPtr
Reindex all files in the transaction list, re-evaluating the key expression in
the process.
This routine is used to reindex up to 256 index files per call. The index
files must already exist and be open. Any existing key values are overwritten
by new key data. In other words, if you have a 100MB index file, REINDEX_XB
uses the same file space, building new keys over old. This results in a less
fragmented disk and also minimizes disk space needed. You can also create a
new, empty index file and reindex to that. This would be useful, for
instance, if you needed to create a temporary index file -- something that
you'd use for a report, say, then delete after the report. Another use for
creating a new index file and reindexing to that is to, after creating it
(COPY_INDEX_HEADER_XB can be used), use EXPAND_FILE_DOS and expand it to the
expected size. This has the benefit of ensuring that this file allocation is
as contiguous as the file system allows (without relying on OS/API-specific
calls).
If the routine failed to complete, the function return value is the number
(1-based) of the pack that caused the failure. A positive number indicates
the failure was from an index operation; a negative number indicates the
failure was from a data operation (reading the data file). In each case, the
absolute value of the return code is the list item that failed (the pack
index). For example, if five index handles are in the list(AP[0] to AP[4]),
and an error occurred on the last pack's index file, the return code would be
positive 5, indicating the fifth pack (AP[4]) failed. Since it was a positive
5, the index file was being processed when the error occurred. Being
processed means not only physical access, but verification, etc. If the
return code was -5, then again, the error was in the fifth pack, but since it
is negative, the error occurred while processing the data file.
Unlike INSERT_XB and UPDATE_XB, each pack need not include a separate key
buffer; you may share a common key buffer. If duplicate keys are generated in
the reindex process and the sort function does not flag DUPS_ALLOWED, an error
is returned. The duplicate key is in *AP.keyPtr and the record number it was
generated from in AP.recNo (both these are valid only on error). Since no
roll-back is performed, there is only a real need for a single key buffer.
You may use separate ones, too.
This routine creates a temporary work file in either the current directory or,
if the environment variable TMP is defined, in the directory pointed to by
TMP=. The path used for this temporary file may also be specified at run-time
by using the TMP_PATH_PTR item for SET_SYSVARS_XB. If TMP_PATH_PTR is NULL
(default), then TMP= is used, or if that is not found, then the current
directory is used. The size of this temporary file is, in bytes,
approximately (keylength+4) * number of records in the data file. The
resultant index files are, by default, optimized for minimum size and maximum
retrieval speed. This full-node packing leaves one empty key per node, which
means b-tree splitting will occur almost immediately upon inserting data (with
INSERT_XB or STORE_KEY_XB).
This behaviour can be modified with the REINDEX_PACK_PCT item for
SET_SYSVARS_XB so that less packing is done. Less packing would improve
subsequent INSERT_XB performance since all nodes are not almost full as they
are with a full pack. File size and retrieval times increase, though, but
perhaps not noticeably.
During the reindex process, each record is checked for a matching skip-tag
value, as set in SET_SYSVARS_XB. The skip-tag is set to 0 by default, where
no check is done and keys for all records in the data file are inserted into
the index file.
ΓòÉΓòÉΓòÉ 9.65. LOCK_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.xlMode
LP.dlMode
LP.recStart=0
LP.nextPtr
Lock all bytes of the files in the list for exclusive use by the current
process, and reload file headers from disk. LP.recStart must be 0 for each
pack.
This routine is used to lock the database for either exclusive use by this
process, or shared access (allowing any process to read, but not write, to the
files). Up to 256 index files may be locked per call, as well as 256 data
files, too, for a total of 512 files per single LOCK_XB call. Shared-access
locking prevents all processes from writing to the file while a shared lock is
in force, including this process. To relock in exclusive lock mode, without
unlocking first, use: RELOCK_XB.
Only index handles are listed in LP.handle. Each index file has associated
with it a data file, known internally to BULLET (the xbLink from OPEN_XB).
There may be more than one index file for a data file, but there is always one
data file per index handle specified in the list. For example, you can list
five index files, each indexing the same xbLink data file, and have BULLET
perform an atomic lock of that list.
LP.xlMode is set to 1 to perform a shared lock on the index file. Set to 0
for an exclusive lock. A shared lock allows only reading.
LP.dlMode is set to 1 to perform a shared lock on the data file. Set to 0 for
an exclusive lock. A shared lock allows only reading.
The lock mode (shared <-> exclusive) can be changed using RELOCK_XB.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones, also act upon this lock count. Therefore,
you can lock a file 100 times in a row, but only on the first lock are any
operations actually performed, and only on the last unlock are any performed.
Other lock/unlock calls (other than the first lock or last unlock) simply
increment or decrement the lock count for that handle.
This and several other routines are transaction-list-based. This means that
if a failure occurs prior to the routine's completion, all locks made to the
database by this routine will be unlocked.
If the routine failed to complete, the function return value is the number
(1-based) of the pack that caused the failure. A positive number indicates
the failure was from an index operation; a negative number indicates the
failure was from a data operation. In each case, the absolute value of the
return code is the list item that failed (the pack index). For example, if
five index handles are in the list(AP[0] to AP[4]), and an error occurred on
the last pack's index file, the return code would be positive 5, indicating
the fifth pack (AP[4]) failed. Since it was a positive 5, the index file was
being processed when the error occurred. Being processed means not only
physical access, but verification, etc. If the return code was -5, then
again, the error was in the fifth pack, but since it is negative, the error
occurred while processing the data file. In either case, upon return, any
files locked during this call are unlocked before returning.
The advantage of using region locks (LOCK_XB locks entire file regions) to
control file access is that the file does not need to be opened/closed using
the Deny Read/Write sharing attribute. Opening the file for Deny None, and
controlling subsequent access with region locks, allows for faster processing
since files do not need to be constantly opened and closed, as they would if
access were controlled by opening with Deny Read/Write.
Using the operating system to control access also prevents other processes
from accessing the files. Other methods, such as internal locking (such as
using 'L' in the tag field as a program-aware in-use flag), work fine so long
as each process accessing the files knows about this internal "locking".
However, since it's proprietary, other processes may not know about it. Any
locking system that is not commonly shared throughout the system is not
effective when it comes to preventing other processes from corrupting files.
Note: Region locking prevents other processes from both writing and reading
the locked file. For operating systems that do not provide shared locks, and
read-access is required at all times, you may specify this type access with
the access-sharing mode when the BULLET file is opened. Once opened for this
(R/W, DenyWrite) then only the current process can write to the file until it
is closed. Other processes must open the file for Read-Only access. For
small networks (two or three nodes), this may be suitable. Otherwise, region
locking is much preferred, and very much faster, since files do not have to be
opened and closed every time the access state needs to change.
ΓòÉΓòÉΓòÉ 9.66. UNLOCK_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.recStart=0
LP.nextPtr
Unlock all bytes in the specified files (previously locked) and flush the
files' headers to disk (the flush is done before the locks are released). Also
unlock all bytes in the related data file and flush the data file header to
disk. LP.recStart must be 0 for each pack.
Note: If a shared-lock is active for this handle (as set by this process),
the flush is not performed. This because writing to the locked region is not
permitted (nor is the flush required since nothing could have been changed).
If the routine failed to complete, the function return value is the number
(1-based) of the pack that caused the failure. A positive number indicates
the failure was from an index operation; a negative number indicates the
failure was from a data operation. In each case, the absolute value of the
return code is the list item that failed (the pack index). For example, if
five index handles are in the list(AP[0] to AP[4]), and an error occurred on
the last pack's index file, the return code would be positive 5, indicating
the fifth pack (AP[4]) failed. Since it was a positive 5, the index file was
being processed when the error occurred. Being processed means not only
physical access, but verification, etc. If the return code was -5, then
again, the error was in the fifth pack, but since it is negative, the error
occurred while processing the data file.
This routine does not attempt to re-lock those files unlocked successfully if
an error occurs in the transaction. If an error does occur (unlikely), the
remaining files must be manually unlocked with the UNLOCK_KEY_XB and
UNLOCK_DATA_XB routines.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones, also act upon this lock count. Therefore,
you can lock a file 100 times in a row, but only on the first lock are any
operations actually performed, and only on the last unlock are any performed.
Other lock/unlock calls (other than the first lock or last unlock) simply
increment or decrement the lock count for that handle.
ΓòÉΓòÉΓòÉ 9.67. LOCK_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.xlMode
Lock all bytes of the index file for exclusive use by the current process and
reload the index file's header from disk.
LP.xlMode is set to 1 to perform a shared lock. Set to 0 for an exclusive
lock. A shared lock allows only reading. The lock mode (shared <->
exclusive) can be changed using RELOCK_INDEX_XB.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones, also act upon this lock count. Therefore,
you can lock a file 100 times in a row, but only on the first lock are any
operations actually performed, and only on the last unlock are any performed.
Other lock/unlock calls (other than the first lock or last unlock) simply
increment or decrement the lock count for that handle.
ΓòÉΓòÉΓòÉ 9.68. UNLOCK_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
Unlock all bytes in the specified file (previously locked) and flush the
file's header to disk (the flush is done before the locks are released).
If the current lock state is shared, the flush is not performed.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones, also act upon this lock count. Therefore,
you can lock a file 100 times in a row, but only on the first lock are any
operations actually performed, and only on the last unlock are any performed.
Other lock/unlock calls (other than the first lock or last unlock) simply
increment or decrement the lock count for that handle.
ΓòÉΓòÉΓòÉ 9.69. LOCK_DATA_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.dlMode
LP.recStart
LP.recCount
Lock all bytes of the data file specified in LP.handle for exclusive use by
the current process. It also reloads the data file header from disk. You must
set LP.recStart=0 to lock all bytes. To lock a single record, or a set of
contiguous records, set LP.recStart to the first record to lock and
LP.recCount to the number of records to lock.
Header re-loading is performed only if locking all bytes.
If LP.recStart is not 0, be aware that the header is not locked, nor is it
re-loaded. Also, maintaining a lock on the single record prevents any other
process from doing a full lock on that data file, thereby preventing write
access to the file from any other BULLET process using LOCK_XB, but not
necessarily preventing other applications from writing to that file. That may
or may not be good. It does not prevent other BULLET processes from reading
that file (except for that locked record).
Multiple single records are allowed, but each must then be unlocked
individually, in the same format (start, count) as the lock.
LP.dlMode is set to 1 to perform a shared lock. Set to 0 for an exclusive
lock. A shared lock allows only reading. The lock mode (shared <->
exclusive) can be changed using RELOCK_DATA_XB.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones if a full lock, also act upon this lock
count. Therefore, you can lock a file 100 times in a row, but only on the
first lock are any operations actually performed, and only on the last unlock
are any performed. Other lock/unlock calls (other than the first lock or last
unlock) simply increment or decrement the lock count for that handle.
ΓòÉΓòÉΓòÉ 9.70. UNLOCK_DATA_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.recStart
LP.recCount
Unlock all bytes in the specified file handle (previously locked) and flush
the data file header to disk (the flush is done before the lock is released).
You must set LP.recStart=0 to unlock all bytes. To unlock a single record, or
a set of contiguous records, set LP.recStart to the first record to unlock and
LP.recCount to the number of records to unlock.
Header flushing is performed only if unlocking a full lock.
An unlock must exactly mimic its corresponding lock. This means if you lock
several records singly, you must unlock each of those records -- you cannot
use LP.recStart=0 to unlock singly-locked records.
Bullet maintains a per-handle lock count, and does a physical region lock only
upon the first lock request (or on a relock request). It is only on this
first lock request that the header is reloaded. When the lock count returns
to 0 (from UNLOCK_XB calls), it is at that time the header is flushed, if
required. Only full-locks are maintained in this way. The number of
outstanding locks can be determined from the SIP and SDP structures, from the
STAT_XB routines. Note that individual LOCK_INDEX_XB and UNLOCK_INDEX_XB
routines, as well as the data ones if a full lock, also act upon this lock
count. Therefore, you can lock a file 100 times in a row, but only on the
first lock are any operations actually performed, and only on the last unlock
are any performed. Other lock/unlock calls (other than the first lock or last
unlock) simply increment or decrement the lock count for that handle.
ΓòÉΓòÉΓòÉ 9.71. CHECK_REMOTE_XB ΓòÉΓòÉΓòÉ
Pack: REMOTEPACK Source Example
IN OUT
RP.func RP.stat
RP.handle RP.isRemote
-or- RP.flags=0
RP.drive RP.isShare=1
If RP.handle is non-zero, determine if the specified handle of a file or
device is remote. If the handle is local (e.g., not a network file or
device), RP.isRemote returns 0, otherwise it is remote. RP.flags=0 and
RP.isShare=1 on return for either a handle or drive check under OS/2.
If RP.handle is zero, determine if the drive specified in RP.drive is remote.
Drive A: is 1, B: is 2, C: is 3, and so on. To check the default drive use 0
(the default drive is the current drive). If the drive is local (e.g., not a
network drive), RP.isRemote returns 0, otherwise it is remote.
The significance of the remote-ness is less important in a multitasking
environment since sharing of resources (files, in particular) must always be
managed, compared to single-tasking environments where, typically, sharing
(locking mechanisms) need only be performed when the resource is able to be
accessed by another process (ie is a 'network' drive). Note that the resource
need not be located elsewhere to be classified as remote: Drives or devices or
files on the same machine may be classified as remote if the network software
is redirecting local access (such as on a server).
Note: This routine is not mutex-protected.
ΓòÉΓòÉΓòÉ 9.72. RELOCK_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.xlMode
LP.dlMode
LP.recStart=0
LP.nextPtr
Relock all bytes of the index files for the mode specified in LP.xlMode (index
files) and LP.dlMode (data files). Also relock all bytes in the related data
file. LP.recStart must be 0 for each pack.
Set LP.xlMode=1 to select a shared lock for the index file; set to 0 for an
exclusive lock. Set LP.dlMode=1 to select a shared lock for the data file;
set to 0 for an exclusive lock.
If the lock mode is from exclusive to shared, the file is flushed before the
shared state is set. BULLET maintains the current lock state and knows which
direction the lock is going in. The lock state (shared or exclusive) can be
determined by the SIP and SDP structures from the STAT_XB routines. This
routine does not affect the lock count, nor are the headers reloaded (nor
should they be).
The lock state is on a file handle basis, not on an LP[] pack basis. This
means the file, as identified by the handle, is in the lock state last set.
Note: The lock switch is made atomic: Rather than unlocking, and then
locking again in the new state, this performs all operations without the
possibility that another process can grab the lock away, if supported by the
OS. If relock is not supported by the OS, and shared (read-only) locks are
supported, the relock will be performed by Bullet rather than the OS. If
Bullet performs the relock, another, non-Bullet process may be able to steal
away the lock since it is no longer guaranteed to be atomic.
ΓòÉΓòÉΓòÉ 9.73. RELOCK_INDEX_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.xlMode
Relock all bytes of the index file for the mode specified in LP.xlMode and
reload the header.
Set LP.xlMode=1 to select a shared lock; set to 0 for an exclusive lock. If
the lock mode is from exclusive to shared, the file is flushed before the
shared state is set. BULLET maintains the current lock state and knows which
direction the lock is going in. The lock state (shared or exclusive) can be
determined by the SIP structure from the STAT_INDEX_XB routine. This routine
does not affect the lock count, nor is the header reloaded (nor should it be).
Note: The lock switch is made atomic: Rather than unlocking, and then
locking again in the new state, this performs all operations without the
possibility that another process can grab the lock away, if supported by the
OS. If relock is not supported by the OS, and shared (read-only) locks are
supported, the relock will be performed by Bullet rather than the OS. If
Bullet performs the relock, another, non-Bullet process may be able to steal
away the lock since it is no longer guaranteed to be atomic.
ΓòÉΓòÉΓòÉ 9.74. RELOCK_DATA_XB ΓòÉΓòÉΓòÉ
Pack: LOCKPACK Source Example
IN OUT
LP.func LP.stat
LP.handle
LP.dlMode
LP.recStart
LP.recCount
Relock all bytes of the data file for the mode specified in LP.dlMode and
reload the header, unless LP.recStart is non-zero.
If the lock mode is from exclusive to shared, the file is flushed before the
shared state is set. BULLET maintains the current lock state and knows which
direction the lock is going in. The lock state (shared or exclusive) can be
determined by the SDP structure from the STAT_DATA_XB routine. This routine
does not affect the lock count, nor is the header reloaded (nor should it be).
You must set LP.recStart=0 to relock all bytes. To relock a single record, or
set of contiguous records, set LP.recStart=record# to relock and LP.recCount
to the number of records to relock.
Note: The lock switch is made atomic: Rather than unlocking, and then
locking again in the new state, this performs all operations without the
possibility that another process can grab the lock away, if supported by the
OS. If relock is not supported by the OS, and shared (read-only) locks are
supported, the relock will be performed by Bullet rather than the OS. If
Bullet performs the relock, another, non-Bullet process may be able to steal
away the lock since it is no longer guaranteed to be atomic.
ΓòÉΓòÉΓòÉ 9.75. DELETE_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr
Delete the specified file.
Note: OS/2 DosForceDelete is used so the file is not recoverable with the
UNDELETE command.
ΓòÉΓòÉΓòÉ 9.76. RENAME_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr
DFP.newNamePtr
Rename a file. May also be used to move the file to a new directory within
the partition.
If the specified directory differs from the file's directory, the file's
directory entry is moved to the new directory.
For example, if the filenamePtr filename is /LP100/PROJ94A.INF and the
newFilenamePtr filename is /ARCH/PROJ93A.INA, the file is essentially renamed
and also moved to the /ARCH directory.
ΓòÉΓòÉΓòÉ 9.77. CREATE_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr
DFP.attr
Create a new file.
The specified filename/pathname must not already exist.
The file created is not left open. You must OPEN_FILE_DOS to use it.
The attribute used during the create can be:
Attribute Value Meaning
Normal 0 normal access permitted to file
Read-Only 1 read-only access permitted to file
Hidden 2 file does not appear in directory listing
System 4 file is a system file
SubDir 10h FILENAME is a subdirectory
Archive 20h file is marked for archiving
Note: Use MAKE_DIR_DOS to create a subdirectory.
ΓòÉΓòÉΓòÉ 9.78. OPEN_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr DFP.handle
DFP.asMode
Open the file with the access-sharing mode, returning the handle on success.
ΓòÉΓòÉΓòÉ 9.79. SEEK_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle DFP.seekTo
DFP.seekTo
DFP.method
Position the file pointer of the file to the seekTo position based on the
method specified.
The position is a 32-bit value and is relative to either the start of the
file, the current file pointer position, or the end of the file.
Method Meaning
0 start move from the start of file (offset is a 32-bit unsigned value)
1 start move at the current position (offset a signed value)
2 start move at the end of file (offset a signed value)
For example, to move to the last byte of a sector (512th byte, but offset
511), set the offset value to 511 and use Method 0. On return, the absolute
offset value of the new position is returned. This return value is useful
with Method 2 since you can specify an offset of 0 and have the file length
returned. To move to the start of the file, use method 0, offset 0. To move
to the first byte of the second sector, use offset 512.
Note: Never position the file pointer to before the start of file.
ΓòÉΓòÉΓòÉ 9.80. READ_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle DFP.bytes
DFP.bytes
DFP.bufferPtr
Read from the file or device the specified number of bytes into a buffer.
On block devices (such as disks) input starts at the current file position and
the file pointer is repositioned to the last byte read +1.
It is possible to read less than the bytes specified without an error being
generated. Compare the bytes to read with the returned bytes read value. If
less then end of file was reached during the read. If 0 then file was already
at EOF.
ΓòÉΓòÉΓòÉ 9.81. WRITE_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle DFP.bytes
DFP.bytes
DFP.bufferPtr
Write to the file or device the specified number of bytes from a buffer.
If the number of bytes written is less than the specified bytes, this routine
returns an error.
On block devices (such as disk) output starts at the current file position,
and the file pointer is repositioned to the last byte written +1.
Note: If the specified bytes to write is 0, the file is truncated at the
current file pointer position.
ΓòÉΓòÉΓòÉ 9.82. CLOSE_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle
Close the file flushing any internal buffers, releasing any locked regions,
and updating the directory entry to the correct size, date, and time.
ΓòÉΓòÉΓòÉ 9.83. ACCESS_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr
DFP.asMode
Determine if the specified file can be accessed with the specified
access-sharing mode.
Basically, a Does-File-Exist routine. It uses the specified access-sharing
mode when trying to open the file. For example, if you specify DFP.attr =
0x0042 (R/W access + Deny None sharing) and issue ACCESS_FILE_DOS on a
Read-Only file, an error is returned. A sharing mode must be specified; it
cannot be left 0.
ΓòÉΓòÉΓòÉ 9.84. EXPAND_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle
DFP.bytes
Expands the file by the number of bytes beyond its current size.
This routine is useful in pre-allocating disk space. By reserving disk space
in advance you can guarantee that enough disk space will be available for a
future operation (especially if more than 1 process is running). You'll also
be able ensure that the disk space that a file does use is as contiguous as
possible.
Database systems are dynamic and their files typically allocate new space on
an as-needed basis. This dynamic allocation can cause parts of a file to be
located throughout the disk system, possibly affecting performance
drastically. By pre-allocating the disk space you can be assured of
consistent throughput performance since the file is contiguous.
Note: The file space is not initialized.
* ---------------------------------------------------------------------------
ΓòÉΓòÉΓòÉ 9.85. MAKE_DIR_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.filenamePtr
Create a new subdirectory.
ΓòÉΓòÉΓòÉ 9.86. COMMIT_FILE_DOS ΓòÉΓòÉΓòÉ
Pack: DOSFILEPACK Source Example
IN OUT
DFP.func DFP.stat
DFP.handle
Flushes the OS system buffers for the handle, and updates the directory entry
for size.
ΓòÉΓòÉΓòÉ 10. Bullet Source Examples ΓòÉΓòÉΓòÉ
Bullet source example excerpts for the C language follow. Complete program
source examples are provide on the included disk.
In the examples, consider that
CHAR keyBuffer[64];
AP.keyPtr = keyBuffer;
provides the compiler with a pointer whenever 'keyBuffer' is referenced, hence
no need to use &keyBuffer. Contrast this with a structure definition
struct yourRecordLayout yourRecord;
AP.recPtr = &yourRecord;
where &yourRecord is required.
Be aware of how you use zero-terminated strings, especially for fields that are
not to be 0T'ed (DATE and NUMERIC fields, for example). Use sscanf() and
sprintf(), as required.
ΓòÉΓòÉΓòÉ 10.1. Bullet Initialization ΓòÉΓòÉΓòÉ
/* All example source uses minimal error checking/handling throughout */
// comment marks are used throughout
// To simplify the source examples, error handling is typically of the form:
//
// if (rez) goto ErrorHandler;
//
// Use suitable coding as you would normally handle the error.
#include "bullet_2.h"
INITPACK IP; // packs used here
IP.func = INIT_XB; // start up Bullet
IP.JFTsize = 30; // allow at least 30 handles to be open
rez = BULLET(&IP);
if (rez) return(rez);
ΓòÉΓòÉΓòÉ 10.2. Bullet Shutdown ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
EXITPACK EP; // packs used here
EP.func = EXIT_XB; // shutdown Bullet
rez = BULLET(&EP);
if (rez) return(rez);
ΓòÉΓòÉΓòÉ 10.3. Memory Available ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
MEMORYPACK EP; // packs used here
MP.func = MEMORY_XB;
rez = BULLET(&MP);
if (rez==0) {
printf("Private memory arena space right now is %d bytes\n",MP.memory);
}
ΓòÉΓòÉΓòÉ 10.4. File Backup Procedure ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
// code determines file type of handle, and selects appropriate backup method
// this type of work (copy/backup) would be a prelude to reindexing an index
// file or performing a pack on a data file since both of those routines
// overwrite existing data
COPYPACK CP;
LOCKPACK LP;
STATHANDLEPACK SHP;
STATDATAPACK SDP;
STATINDEXPACK SIP;
CHAR CopyOfIndexHdrName[260];
CHAR BackupDataName[260];
// Given a handle, find out its type and, if index, generate a copy
// of the index file's header, or if data, backup the entire data file.
// Files are locked to ensure that access if permissible.
SHP.func = STAT_HANDLE_XB;
SHP.handle = passedHandle;
rez = BULLET(&SHP);
if (SHP.ID==-1)
puts("Handle is not a Bullet data or index file. Use DosCopy() API call.");
else {
if (SHP.ID==0) {
// Since the copy takes place on open files, ensure a full-lock is in place
// A shared lock is okay since this file is only read
// locking the entire file also RELOADS the index header
LP.func = LOCK_INDEX_XB;
LP.handle = passedHandle;
LP.xlMode = LOCK_SHARED; // in bullet_2.h
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
SIP.func = STAT_INDEX_XB;
SIP.handle = passedHandle;
rez = BULLET(&SIP);
if (rez) goto ErrorHandler; // must unlock file in handler
// SIP.filenamePtr -> pathname of this handle, from which you can
// derive a suitable name for the index header written next -- in
// case it's not obvious, this function is not part of Bullet
DeriveSuitableName(SIP.filenamePtr,CopyOfIndexHdrName);
CP.func = COPY_INDEX_HEADER_XB;
CP.handle = passedHandle;
CP.filenamePtr = CopyOfIndexHdrName;
rez = BULLET(&CP);
if (rez) goto ErrorHandler; // must unlock file in handler
// unlocking also flushes the index header if not LOCK_SHARED (and if required)
LP.func = UNLOCK_INDEX_XB;
LP.handle = passedHandle;
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
}
else {
// locking the entire file also RELOADS the data header
LP.func = LOCK_DATA_XB;
LP.handle = passedHandle;
LP.dlMode = LOCK_SHARED;
LP.startRec = 0; // lock entire data file
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
SDP.func = STAT_DATA_XB;
SDP.handle = passedHandle;
rez = BULLET(&SDP);
if (rez) goto ErrorHandler; // must unlock file in handler
DeriveSuitableName(SDP.filenamePtr,BackupDataName);
CP.func = BACKUP_FILE_XB;
CP.handle = passedHandle; // set to -passedHandle to skip memo backup
CP.filenamePtr = BackupDataName;
rez = BULLET(&CP);
if (rez) goto ErrorHandler; // must unlock file in handler
// unlocking also flushes the data header if not LOCK_SHARED (and if required)
LP.func = UNLOCK_DATA_XB;
LP.handle = passedHandle;
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
}
}
ΓòÉΓòÉΓòÉ 10.5. Get Error Class ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
XERRORPACK XEP; // packs used here
// Bullet errors range from 8192 to 8999, any other return code
// indicates the error number was generated by OS/2 itself
if (rez < 8192) | (rez > 8999) {
XEP.func = GET_ERROR_CLASS_XB;
XEP.stat = rez;
rez = BULLET(&XEP);
// here XEP.errClass, .action, and .location are set
// this call is the same as OS/2 API DosErrClass()
ΓòÉΓòÉΓòÉ 10.6. Query System Variables ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
QUERYSETPACK QSP; // packs used here
// Query a Bullet system variable
QSP.func = QUERY_SYSVARS_XB;
QSP.item = MUTEX_SEM_HANDLE; // in bullet_2.h
rez = BULLET(&QSP);
if (rez==0)
printf("Bullet/2 mutex handle is %d\n",QSP.itemValue);
ΓòÉΓòÉΓòÉ 10.7. Set System Variables ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
QUERYSETPACK QSP; // packs used here
// Set a Bullet system variable
QSP.func = SET_SYSVARS_XB;
QSP.item = REINDEX_BUFFER_SIZE; // in bullet_2.h
QSP.itemValue = 384*1024; // set to 384KB
rez = BULLET(&QSP);
if (rez==0) {
printf("Reindex buffer sized changed to 384K.\n");
printf("Previous setting was %d.\n",QSP.itemValue);
// For REINDEX_BUFFER_SIZE, a value of 0 represents 'autosize', for which
// Bullet selects a minimum usuable size (often 144KB). The minimum size
// that you can use for REINDEX_BUFFER_SIZE is 48KB.
ΓòÉΓòÉΓòÉ 10.8. Set Dual-video Monitor ΓòÉΓòÉΓòÉ
// SET_DVMON_XB is not currently used
ΓòÉΓòÉΓòÉ 10.9. Query Vectors ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
QUERYSETPACK QSP; // packs used here
// Query a Bullet vector
QSP.func = QUERY_VECTORS_XB;
QSP.item = VECTOR_GET_SORT_TABLE; // in bullet_2.h
rez = BULLET(&QSP);
if (rez==0)
printf("Bullet get-sort-table vector is %x\n",QSP.itemValue);
ΓòÉΓòÉΓòÉ 10.10. Set Vectors ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
QUERYSETPACK QSP; // packs used here
// Set a Bullet vector
// external routine (in ccdosfn.c, for example)
LONG __cdecl BulletMalloc(ULONG bytes, VOID **basePtr);
QSP.func = SET_VECTORS_XB;
QSP.item = VECTOR_MALLOC;
QSP.itemValue = (ULONG) &BulletMalloc;
rez = BULLET(&QSP);
if (rez==0) {
printf("Memory allocation changed to new routine.\n");
printf("Previous routine's address was %x.\n",QSP.itemValue);
ΓòÉΓòÉΓòÉ 10.11. Create, Open, and Close Data and Index Files ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
CREATEDATAPACK CDP;
CREATEINDEXPACK CIP;
OPENPACK OP;
HANDLEPACK HP; // packs used here
// create the data file
#pragma pack(1) // ensure compiler does not pad Bullet-used structures
// (not needed here since all members are CHAR)
// generally, only left-justified strings should be 0-terminated, numbers or
// date fields should not be 0-terminated
typedef struct _RECTYPE {
CHAR tag; // record tag, init to SPACE, '*' means deleted
CHAR userSSN[9]; // first field in DBF (not 0-terminated in this case)
CHAR userScore[6]; // second field (also not 0T)
} RECTYPE; // (total record length is 16 bytes)
RECTYPE ourRecord;
#pragma pack()
CHAR nameIX3[] = "INDEX.IX3"; // index pathname
CHAR keyExpression[] = "SSN"; // key is built from field named 'SSN'
ULONG indexID=0; // handle of opened index file
CHAR keyBuffer[68]; // buffer to store/receive key values
CHAR nameData[] = "DATA.DBF"; // data pathname
ULONG dataID=0; // handle of opened data file
FIELDDESCTYPE fieldList[2]; // 2 fields used in data record (SSN and SCORE)
// the field descriptor info must have unused entries set to 0
memset(fieldList,0,sizeof(fieldList)); // init unused bytes to 0 (required)
// fill in the field descriptor info for the data file you want to create
// this descriptor must match the layout of ourRecord, above (ourRecord.tag
// is implicit and is not a formal field, and so is not in fieldList[])
strcpy(fieldList[0].fieldName, "SSN"); // field names must be upper-case
fieldList[0].fieldType = 'C'; // field types must be upper-case
fieldList[0].fieldLen = 9; // * Note that the .fieldname here
fieldList[0].fieldDC = 0; // * matches the keyExpression
strcpy(fieldList[1].fieldName, "SCORE");
fieldList[1].fieldType = 'C';
fieldList[1].fieldLen = 6; // 6 is total size of field
fieldList[1].fieldDC = 0;
// Create the data file as defined in fieldList above
// To create only a DBF, set CDP.fileID=3
// To create both a DBF and a DBT memo file, set CDP.fileID=0x8B
CDP.func = CREATE_DATA_XB;
CDP.filenamePtr = nameData;
CDP.noFields = 2;
CDP.fieldListPtr = fieldList;
CDP.fileID = 3;
rez = BULLET(&CDP);
if (rez) {
printf("Failed data file create. Err: %d\n",rez);
return(rez);
}
// Open the data file (required before creating an index file for it)
OP.func = OPEN_DATA_XB;
OP.filenamePtr = nameData;
OP.asMode = READWRITE | DENYNONE;
rez = BULLET(&OP);
if (rez) {
printf("Failed data file open. Err: %d\n",rez);
return(rez);
}
dataID = OP.handle;
// Create an index file for the data file opened above.
// This example uses a simple primary key: the SSN field.
// Since it is assumed to be unique, DUPS_ALLOWED is not
// OR'ed with the .sortFunction.
CIP.func = CREATE_INDEX_XB;
CIP.filenamePtr = nameIX3;
CIP.keyExpPtr = keyExpression;
CIP.xbLink = dataID; // the handle of the data file
CIP.sortFunction = ASCII_SORT; // sort key by ASCII (fine for SSN ordering)
CIP.codePage = 0; // use OS-default code page
CIP.countryCode = 0; // use OS-default country code
CIP.collatePtr = NULL; // no need for a special collate table
CIP.nodeSize = 512; // 512-byte node size (or 1024, 2048 bytes)
rez = BULLET(&CIP);
if (rez) {
printf("Failed index file create. Err: %d\n",rez);
return(rez);
}
// Open the index file (what we just created above).
// As with the index-create, the index-open requires the handle of the data
// file which this index file indexes.
OP.func = OPEN_INDEX_XB;
OP.filenamePtr = nameIX3;
OP.asMode = READWRITE | DENYNONE;
OP.xbLink = dataID;
rez = BULLET(&OP);
if (rez) {
printf("Failed index file open. Err: %d\n",rez);
return(rez);
}
indexID = OP.handle;
// at this point, both the data and index files are open and accessible
// |
// | do work as required, then, when done, close them
// |
if (indexID) {
HP.func = CLOSE_INDEX_XB;
HP.handle = indexID;
rez = BULLET(&HP);
if (rez)
printf("Failed index file close. Err: %d\n",rez);
}
if (dataID) {
HP.func = CLOSE_DATA_XB;
HP.handle = dataID;
rez = BULLET(&HP);
if (rez)
printf("Failed data file close. Err: %d\n",rez);
}
ΓòÉΓòÉΓòÉ 10.12. Read Data and Index Header ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
HANDLEPACK HP; // packs used here
// Since it is recommended that a full-lock be in force before reloading
// data or index headers, and since performing a full lock from BULLET does
// itself reload the header, this routine would not normally be used.
// However, if you are doing your own locking, then you need to call this.
rez = YourExternalControlLockRoutine; // so you have your own locks...
if (rez==0) {
if (youWantDataReload) {
HP.func = READ_DATA_HEADER_XB;
HP.func = dataID;
}
else {
HP.func = READ_INDEX_HEADER_XB;
HP.func = indexID;
}
rez = BULLET(&HP);
// since locked, release lock regardless rez value
rez2 = YourExternalControlUnlockRoutine;
}
// Be sure to read the explanation above
ΓòÉΓòÉΓòÉ 10.13. Flush Data and Index Header ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
HANDLEPACK HP; // packs used here
// While BULLET automatically flushes data and index info whenever
// a BULLET file is unlocked from a full-lock (and if it
// is needed), you may to flush more frequently.
// It is recommended that you have a full-lock on the file before
// flushing. In normal use, you would always have a full-lock when
// writing to a file (the only time you need to flush is if the file
// has been written to). If you are, for whatever reason, keeping
// the file locked, and are updating it repeatedly, and have no
// intention of unlocking (and thereby flushing it) any time soon,
// you may do a manual flush to ensure that the disk image matches
// the memory image (in case the power goes out and you have no UPS)
if (youWantDataFlush) {
HP.func = FLUSH_DATA_HEADER_XB;
HP.func = dataID;
}
else {
HP.func = FLUSH_INDEX_HEADER_XB;
HP.func = indexID;
}
rez = BULLET(&HP);
// Only if the file has been changed does a flush actually write to disk.
// You should have an exclusive full-lock; a shared full-lock cannot be
// used since a shared lock does not allow writing to the file.
ΓòÉΓòÉΓòÉ 10.14. Copy (Add) a Subset of Records to a New File ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK APo; // used when accessing original
ACCESSPACK APn; // used when accessing new
COPYPACK CP;
HANDLEPACK HP;
OPENPACK OP; // packs used here
RECTYPE ourRecord; // as defined in Create example
// Assume an open, locked DBF data file, handle in dataID, and that you
// want to copy those records that are marked as deleted, one at a time, to a
// new file, prior to packing the database. An extension to this procedure is
// to access each record in key order, and to copy each record to the new
// file, thereby giving a SORTED data file (also known as a clustered file).
// The lock must be a full-lock, but may be a shared full-lock. A ZAP is done
// on the new file if the copy did not complete as expected.
// No indexed access is used in this example.
CP.func = COPY_DATA_HEADER_XB;
CP.handle = dataID; // dataID is the original data file handle
CP.filenamePtr = "DELRECS.DBF"; // new file for deleted records
rez = BULLET(&CP);
if (rez) {
printf("Failed header copy. Err: %d\n",rez);
return(rez);
}
// DELRECS.DBF now is exactly like the original, but has 0 records. Since
// we're building this file, open it for exclusive use (DENYREADWRTE) --
// this is different from using the LOCK_XB routines since the lock is done
// at the file open level (no other process may even open it), whereas
// the LOCK_XB routines are at the access level (other processes may
// open the file, but may or may not access it). You could instead use
// LOCK_DATA_XB for an exclusive full-lock, but this is simpler.
OP.func = OPEN_DATA_XB;
OP.filenamePtr = "DELRECS.DBF"; // open it
OP.asMode = READWRITE | DENYREADWRITE; // for exclusive use
rez = BULLET(&OP);
if (rez) {
printf("Failed new file open. Err: %d\n",rez);
return(rez);
}
// Now have two files open: the original, with all the records, and
// the new, with no records. The procedure here is to get all original
// records, check each for being deleted, if so, add that record (copy it)
// to the new file. After copying, the original file is packed so that
// all deleted records are removed. Then an index to it is reindexed.
// most BULLET pack members will be invariant within loops... (low overhead!)
APn.func = ADD_RECORD_XB; // AccessPack for the new file
APn.handle = OP.handle; // the handle just opened above
APn.recPtr = &ourRecord;
APo.func = GET_RECORD_XB; // AccessPack for the original file
APo.handle = dataID;
APo.recNo = 1; // start at the first record
APo.recPtr = &ourRecord; // read data into this
rez = BULLET(&APo); // read the first data record
while (rez==0) {
// check if this record is deleted, if so copy it else continue
if (ourRecord.tag = '*') {
rez = BULLET(&APn); // add it to the new file
if (rez) break;
// here APn.recNo is the record number used for the just-added record
}
// This while() loop could have been a for() loop if we had used
// STAT_DATA_XB to get the number of records, but in this example,
// the original file is read until EXB_BAD_RECNO is returned,
// indicating that the last record has been passed.
APo.recNo++; // get next original record
rez = BULLET(&APo); // everything else is already setup
}
// the expected rez here is EXB_BAD_RECNO, any other then quit
if (rez != EXB_BAD_RECNO) {
printf("Failed the while() loop, ZAPing new file. Err: %d\n",rez);
// As an example, if the copy failed to complete as expected, the
// new file is ZAP'ed, removing any records that may have been copied
// up to the point that the error occurred -- investigate failure and
// restart process (you may want to just delete, rather than ZAP/CLOSE).
HP.func = ZAP_DATA_HEADER_XB;
HP.handle = APn.handle; // the new file handle
rez2 = BULLET(&HP); // using 'rez2' to preserve initial error code
if (rez2)
printf("Failed ZAP! Err: %d\n",rez2);
HP.func = CLOSE_DATA_XB; // close
rez2 = BULLET(&HP);
if (rez2)
printf("Failed CLOSE! Err: %d\n",rez2);
return(rez); // return initial error code
}
// done with the new file
// it contains all the records in the original marked as deleted
HP.func = CLOSE_DATA_XB; // always use the correct pack for the routine
HP.handle = APn.handle; // -- do not try to use AP when closing a file
rez = BULLET(&HP);
if (rez) return(rez);
// Pack the original data file, now that we've "saved" the delete-marked recs.
// Note: You may want to use BACKUP_XB before using this next routine
// in case a serious error occurs. BULLET packs in place! Also, before
// packing, memos belonging to records about to be deleted should have
// have been deleted before packing. Neither is shown here.
APo.func = PACK_RECORDS_XB;
rez = BULLET(&APo);
if (rez==0) {
// After a pack, you must reindex any related index files.
// Assume here one index file, handle in indexID
APo.func = REINDEX_XB;
APo.handle = indexID;
rez = BULLET(&APo); // rez is not errc, but is pack # than failed
if (rez)
printf("Failed reindex. Err: %d\n",APo.stat);
}
else {
printf("Failed packed! Recommend restore from backup. Err: %d\n",rez);
printf("Note: Unless error is known to not be severe.\n");
}
// Here and rez==0 then everything went as planned.
return(rez);
ΓòÉΓòÉΓòÉ 10.15. Zap Index File ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
HANDLEPACK HP; // packs used here
// Since BULLET reindexes in place, there's typically no need to use
// ZAP to reduce disk requirements (there won't be two separate file
// spaces since the reindex copies right over the old key data).
HP.func = ZAP_INDEX_HEADER_XB;
HP.handle = indexToZap;
rez = BULLET(&HP);
if (rez) return(rez);
ΓòÉΓòÉΓòÉ 10.16. Get Descriptor, Using a DBF Not Created by Bullet ΓòÉΓòÉΓòÉ
#include <os2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bullet_2.h"
int main(int argc,char *argv[]) {
INITPACK IP;
EXITPACK EP;
DESCRIPTORPACK DP;
OPENPACK OP;
HANDLEPACK HP;
STATDATAPACK SDP;
ACCESSPACK AP; // packs used here
PFIELDDESCTYPE fieldDescPtr; // pointer to field descriptor base allocation
PFIELDDESCTYPE fdPtr; // roving pointer to any field's descriptor
CHAR dataRec[8192]; // 'unknown' record layout since reading "any" DBF
CHAR fmt[32]; // printf() fmt string for on-the-fly formatting
ULONG dataID; // handle of DBF
LONG recNo; // loop counter
BYTE fldNo; // loop counter2
int rez; // primary op return code
int rez2; // secondary op return code (so to perserve primary rc)
if (argc < 2) {
puts("Use: C>progname anyfile.dbf");
return(1);
}
// init Bullet
IP.func = INIT_XB;
IP.JFTsize = 20; // 20 handles is all we need here
rez = BULLET(&IP);
if (rez!=0) {
printf("INIT_XB failed: %d\n",rez);
return(1);
}
// open existing DBF as named in command line
OP.func = OPEN_DATA_XB;
OP.filenamePtr = argv[1];
OP.asMode = READWRITE | DENYNONE;
rez = BULLET(&OP);
if (rez==0) {
dataID = OP.handle;
SDP.func = STAT_DATA_XB;
SDP.handle = dataID;
rez = BULLET(&SDP);
if (rez==0) {
// allocate field descriptors needed (SDP.fields is number needed)
// calloc() used since 0-filled storage is required
fieldDescPtr = calloc(SDP.fields,sizeof(FIELDDESCTYPE));
if (fieldDescPtr != NULL) {
fdPtr = fieldDescPtr; // fdPtr->each descriptor
// read each field descriptor from Bullet, storing to our program
// show each for display
// 1234567890-123456789-123456789-12345
printf("FLD# FIELDNAME T LEN.DEC OFFSET\n");
DP.func = GET_DESCRIPTOR_XB;
DP.handle = dataID;
for (fldNo=1;fldNo <= SDP.fields;fldNo++) {
DP.fieldNumber = fldNo;
rez = BULLET(&DP);
if (rez==0) {
strcpy(fdPtr->fieldName, DP.FD.fieldName);
fdPtr->fieldType = DP.FD.fieldType;
fdPtr->fieldLen = DP.FD.fieldLen;
fdPtr->fieldDC = DP.FD.fieldDC;
fdPtr->fieldDA = DP.fieldOffset;
printf("%3u %-10s %c %3u.%1u %4u\n",
fldNo,
fdPtr->fieldName,
fdPtr->fieldType,
(ULONG) fdPtr->fieldLen,
(ULONG) fdPtr->fieldDC,
fdPtr->fieldDA);
fdPtr++; // next field descriptor
}
else
break;
}
// An interesting item above is where fdPtr->fieldDA is set to
// DP.fieldOffset. fieldDA is a run-time storage area that in
// dBASE is used to directly access the field (DA="direct access").
// It has no meaning except for that particular run (it is a memory
// address). In this program example I use it to store the offset
// of the field, relative the start of the record buffer (where the
// tag byte = offset 0). You could just as easily use some of the
// 12 reserved bytes left over in the descriptor, as I do for the
// alternate field length. But, since fieldDA is already there, and
// not used otherwise, it makes sense to use it.
// Now have all we need to know about the DBF fields, having just
// read and stored the field descriptors. For this example, we
// grab the first nine records and spit them out, by field, in record
// number order (no indexing used).
if (SDP.records != 0) {
AP.func = GET_RECORD_XB;
AP.handle = dataID;
AP.recPtr = &dataRec;
for (recNo=1;recNo <= 9; recNo++) {
printf("\nrecNo %u: ",recNo); // show line number
AP.recNo = recNo; // get this record #...
rez = BULLET(&AP); // ...to dataRec buffer
if (rez==0) {
printf("%.1s ",(CHAR *) dataRec); // show if deleted or not
fdPtr = fieldDescPtr; // fdPtr->first field descriptor
for (fldNo=1;fldNo <= SDP.fields;fldNo++) {
// No special formatting is done on this output for this
// example -- since standard DBF data is always in pure
// ASCII form, all is printable.
switch (fdPtr->fieldType) {
case 'C': // text
case 'D': // date, show as-is
case 'L': // logical, show as-is
case 'M': // memo field (block number in ASCII)
case 'N': // numeric (ASCII)
// make fmt[] string like this: "%xx.xxs"
// where xx is field length for this field
sprintf(fmt,"%%-%i.%is ",
fdPtr->fieldLen,
fdPtr->fieldLen);
// fdPtr->fieldDA=offset of the field within the record
// so it plus dataRec (buffer base) results in the
// offset of the current field we are processing
printf(fmt,dataRec+fdPtr->fieldDA);
break;
default:
printf("\nUnknown field type: %c\n",fdPtr->fieldType);
} // switch
fdPtr += 1; // next field's descriptor
} // for fields
} // if record read
else {
if (rez==EXB_BAD_RECNO) // if < for-count records in DBF
rez=0; // then would get this error
else
printf("Failed GET_RECORD_XB, err: %d\n",rez);
break; // break for any ELSE case
}
} // for records
if (rez==0) printf("\nDone.\n"); // all FOR recs done
}
else
printf("No records in file\n");
free(fieldDescPtr);
}
else
printf("calloc failed!\n");
}
else
printf("STAT_DATA_XB failed: %d\n",rez);
HP.func = CLOSE_DATA_XB;
HP.handle = dataID;
rez2 = BULLET(&HP);
}
else
printf("OPEN_DATA_XB failed: %d\n",rez);
EP.func = EXIT_XB;
rez2=BULLET(&EP);
printf("\nPress ENTER to exit");
getchar();
if (rez==0) rez=rez2; // rez is more important, but if 0 use rez2 result
return(rez);
}
The above is a complete program. Running it against a sample DBF results in
the following output:
FLD# FIELDNAME T LEN.DEC OFFSET
1 SSN C 9.0 1
2 LNAME C 16.0 10
3 FNAME C 16.0 26
4 HIRED D 8.0 42
5 DEPT_ID C 6.0 50
recNo 1: 465309999 Que Barbie 19900131 BOSS
recNo 2: 445038888 Stewart Jackie 19910228 ACC
recNo 3: 760443232 Whitman Kelly 19920414 HUM
recNo 4: 845309944 Beatty Leslie 19940122 PRG
recNo 5: 555033388 Jasper Amy 19930230 PRG
recNo 6: 430443222 Hauntos Poco 19920414 PRG
recNo 7: 365502949 Hopkins Lisa 19910121 PRG
recNo 8: 685733868 Leonard Rosina 19850218 PRG
recNo 9: 500945242 Morton Holly 19950406 PHY
Done.
Press ENTER to exit
ΓòÉΓòÉΓòÉ 10.17. Update a Data Record ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP; // packs used here
typedef struct _RECTYPE {
CHAR tag; // record tag, init to SPACE, '*' means deleted
CHAR userSSN[9]; // first field in DBF (not 0-terminated in this case)
CHAR userScore[6]; // second field (also not 0T)
} RECTYPE; // (total record length is 16 bytes)
RECTYPE ourRecord;
// This excerpt demonstrates how to update (change) a record.
// The idea is to get the current contents of a record (by record
// number since no index is used for this update routine), change
// what needs changing, then write it back. Under no circumstances
// should you change any field that is used as a key by an index, or
// as a foreign key, or in any other way removes the referential
// integrity of the database. If you need to change a key field, then
// you must use UPDATE_XB.
AP.func = GET_RECORD_XB;
AP.handle = dataID;
AP.recNo = recordToUpdate;
AP.recPtr = &ourRecord;
rez = BULLET(&AP);
if (rez) return(rez);
// ourRecord has data stored at record number recordToUpdate -- since
// userScore is not used as a key field, this routine may be used to
// modify the contents of that field. Since numbers are stored as ASCII
// text in compatible DBF files, must convert to binary, perform needed
// math, then convert back to text:
//t = atol(ourRecord.userScore); // use scanf() since not 0T
sscanf(t,"%6u",&ourRecord.userScore);
t = t + ClassCurve; // increase each score by curve value
sprintf(ourRecord.userScore,"%6.6u",t)
AP.func = UPDATE_RECORD_XB; // other AP values already set up from GET
rez = BULLET(&AP); // write out the record with the new score
if (rez) return(rez);
ΓòÉΓòÉΓòÉ 10.18. Delete, Undelete, 'Debump' a Data Record ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP; // packs used here
// delete or undelete or remove (debump)
switch(*requestMsg) {
case 'delete':
AP.func = DELETE_RECORD_XB; // places a '*' in .tag byte
break;
case 'undelete':
AP.func = UNDELETE_RECORD_XB; // places a SPACE in .tag byte
break;
case 'debump':
AP.func = DEBUMP_RECORD_XB; // physically removes record from file, but
break; // fails if AP.recNo is not last record number
}
AP.handle = dataID; // same for all three
AP.recNo = recordNumber;
rez = BULLET(&AP);
return(rez);
ΓòÉΓòÉΓòÉ 10.19. Reading Memo Records ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP;
LOCKPACK LP;
MEMODATAPACK MDP; // packs used here
typedef struct _RECTYPE {
CHAR tag; // record tag, init to SPACE, '*' means deleted
CHAR userSSN[9]; // first field in DBF (not 0-terminated in this case)
CHAR userMemo[10]; // second field (also not 0T), is memo field type
} RECTYPE;
RECTYPE someRecord;
// a minimum shared record lock is required on the DBF record that owns
// memoNumber in this example, since only reading of the single memo is done
LP.func = LOCK_DATA_XB;
LP.handle = dataID;
LP.dlMode = LOCK_SHARED; // only reading here, shared record lock is okay
LP.recStart = recordToGet; // lock this record
LP.recCount = 1; // and only this record
rez = BULLET(&LP);
if (rez) return(rez);
// hereafter, must unlock before exiting
AP.func = GET_RECORD_XB;
AP.handle = dataID;
AP.recNo = recordToGet;
AP.recPtr = &someRecord; // load someRecord with data on disk
rez = BULLET(&AP);
if (rez) return(rez); // UNLOCK! before doing this exit
// memoNumber = atol(someRecord.userMemo); // the memo number (0 if none)
sscanf(memoNumber,"%10u",&someRecord.userMemo);
// this code reads the number of data bytes in the memo and allocates a
// run-time buffer to read that memo into
MDP.func = GET_MEMO_SIZE_XB;
MDP.dbfHandle = dataID; // handle of the DBF this memo belongs to
MDP.memoNo = memoNumber; // memo number to get size of (1=first)
rez = BULLET(&MDP); // (returns an error if memoNumber is 0)
if (rez==0) {
// BULLET does maintain 0-sized memo records, so you may want to check
// if MDP.memoBytes from GET_MEMO_SIZE_XB is 0, and skip processing if so
memoBytesToRead = MDP.memoBytes; // since overwritten by next call
memoBufferPtr = malloc(memoBytesToRead); // assuming you want it all at once
if (memoBufferPtr) {
MDP.func = GET_MEMO_XB;
MDP.dbfHandle = dataID; // same as before, as is .memoNo
MDP.memoNo = memoNumber;
MDP.memoPtr = memoBufferPtr; // memo disk data is loaded into this buffer
MDP.memoOffset = 0; // read from very first memo data byte
MDP.memoBytes = memoBytesToRead;
// MDP.memoBytes is already set to the total data size -- you may read
// fewer bytes, and you may use .offset to move through the memo data
// chunks at a time, rather than all at once -- the above simply reloads
// it with the same count, since here memoBytesToRead==MDP.memoBytes
rez = BULLET(&MDP); // returns with MDP.memoBytes= bytes read
if (rez==0) {
// if (MDP.memoBytes != memoBytesToRead)
// printf("Could not read all bytes requested - probably at end of memo\n");
// above would not happen in this case since the exact size was requested
// process as required (here passes buffer ptr and bytes actually read)
DoWhatYouWillWithThisMemoData(memoBufferPtr,MDP.memoBytes);
}
free(memoBufferPtr);
}
else rez=8; // malloc failed, return 8=not enough memory
}
LP.func = UNLOCK_DATA_XB;
LP.handle = dataID;
LP.recStart = recordToGet; // unlock this record
LP.recCount = 1; // and only this record
rez2 = BULLET(&LP);
if (rez==0) rez=rez2;
return(rez);
ΓòÉΓòÉΓòÉ 10.20. Add, Update, Delete a Memo Record ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP;
LOCKPACK LP;
MEMODATAPACK MDP; // packs used here
typedef struct _RECTYPE {
CHAR tag;
CHAR userSSN[9];
CHAR userMemo[10];
} RECTYPE;
RECTYPE someRecord;
// an exclusive lock is required on the DBF file that owns this memo file
// since writing is done to the memo file (memo file header, especially)
LP.func = LOCK_DATA_XB;
LP.handle = dataID;
LP.dlMode = LOCK_EXCLUSIVE; // writing here, exclusive full lock required
LP.recStart = 0; // lock all
rez = BULLET(&LP);
if (rez) return(rez);
// hereafter, must unlock before exiting
AP.func = GET_RECORD_XB;
AP.handle = dataID;
AP.recNo = recordToGet;
AP.recPtr = &someRecord; // load someRecord with data on disk
rez = BULLET(&AP);
if (rez) return(rez); // UNLOCK! before doing this exit
// memoNumber = atol(someRecord.userMemo); // the memo number (0 if none)
sscanf(memoNumber,"%10u",&someRecord.userMemo);
// if there is no current memo, this example adds one
// if there is, this example updates it by:
// - changing the first 16 bytes to "Kilroy was here."
// - adding the text "Was updated." to the end of the current memo data
// the example then deletes the memo
CHAR kilroyStr[] = "Kilroy was here.";
CHAR updateStr[] = "Was updated.";
CHAR newStr[] = "New.";
if (memoNumber) {
// modify the current memo -- first, the text "Kilroy was here." is
// placed at the start of the memo, then the memo size is gotten (in
// case the original memo size were less than the size of "Kilroy...").
MDP.func = UPDATE_MEMO_XB;
MDP.dbfHandle = dataID; // handle of the DBF this memo belongs to
MDP.memoNo = memoNumber; // memo number to update (1=first)
MDP.memoPtr = kilroyStr; // data to write
MDP.memoOffset = 0; // start write at first byte of memo
MDP.memoBytes = strlen(kilroyStr); // bytes to write
rez = BULLET(&MDP);
// the first 16 bytes of the memo now say kilroyStr (overwrote what was there)
// the memo size changes only if the original memo was < 16 bytes, in
// which case the size is now 16
if (rez==0) {
printf("%s overwrote first 16 bytes\n",kilroyStr);
// must check if the update resulted in a new memo block being
// used (if the update required more allocation blocks), and if
// so must update the DBF field storing the memo number
// MDP.memoNo is the memo number returned, memoNumber the original number
// IT CAN BE ASSUMED IN THIS PARTICULAR example that this will never
// be needed since the update modified the first 16 bytes of the
// memo only, and so would never have required any more blocks --
// however, unless you know before-hand that the update will not need
// more allocation blocks (not difficult, if you know the block size,
// overhead bytes (8), and your offset and bytes to write), it should
// be checked -- hint: as with all Bullet routines, the idea is to
// wrap up these separate operations into nice, neat callable routines
// that take care of your particular need; there are no doubt 1000s of
// variations -- pick one you can deal with.
if (MDP.memoNo != memoNumber) {
sprintf(someRecord.userMemo,"%10.10u",MDP.memoNo)
memoNumber = MDP.memoNo; // set original to new for next update
// rather than updating here, and possibly again below, you
// may elect to set a flag and then do the DBF update at the end, once
// again -- this updates the _data_ record, in the DBF file:
AP.func = UPDATE_RECORD_XB; // this updates the data record only
AP.handle = dataID;
AP.recNo = recordToGet;
AP.recPtr = &someRecord; // write the new data
rez = BULLET(&AP);
if (rez) return(rez); // UNLOCK! before doing this exit
}
MDP.func = GET_MEMO_SIZE_XB; // other MDP members already set above
rez = BULLET(&MDP);
if (rez=0) { // size of memo is at least 16 (from kilroyStr)
// but may be bigger if was bigger before
MDP.func = UPDATE_MEMO_XB;
MDP.memoPtr = updateStr;
// the current memo size is used as the offset for the appending of
// the updateStr bytes (offset is 0-based, so using MDP.memoBytes
// results in the offset being the current size + 1)
MDP.memoOffset = MDP.memoBytes;
MDP.memoBytes = strlen(updateStr);
rez = BULLET(&MDP); // returns with MDP.memoNo
// bytes written==MDP.memoBytes always
if (rez==0)
printf("%s appended to memo\n",updateStr);
// _minimum_ memo contents now is kilroyStr plus updateStr
// more if original memo was > 16 bytes
if (MDP.memoNo != memoNumber) {
sprintf(someRecord.userMemo,"%10.10u",MDP.memoNo)
AP.func = UPDATE_RECORD_XB; //
AP.handle = dataID; // as explained above
AP.recNo = recordToGet; //
AP.recPtr = &someRecord;
rez = BULLET(&AP);
if (rez) return(rez); // actually must unlock before exit!
}
} // memo update #2 failed
} // memo size failed
}
else
printf("update failed, err: %d\n",rez); // disk full probably
}
else {
// no current memo, add one
MDP.func = ADD_MEMO_XB;
MDP.dbfHandle = dataID; // handle of the DBF this memo belongs to
MDP.memoPtr = newStr; // data to write
MDP.memoBytes = strlen(newStr); // bytes to write
rez = BULLET(&MDP);
if (rez==0) {
sprintf(someRecord.userMemo,"%10.10u",MDP.memoNo)
AP.func = UPDATE_RECORD_XB; //
AP.handle = dataID; // as explained above
AP.recNo = recordToGet; //
AP.recPtr = &someRecord;
rez = BULLET(&AP);
}
else
printf("add failed, err: %d\n",rez);
}
// delete the memo just operated on
if (rez==0) {
MDP.func = DELETE_MEMO_XB;
// MDP.memoNo is already set
rez = BULLET(&MDP);
if (rez==0) {
strcpy(someRecord.userMemo," ");// DBF memo field to all spaces
AP.func = UPDATE_RECORD_XB; //
AP.handle = dataID; // as explained above
AP.recNo = recordToGet; //
AP.recPtr = &someRecord;
rez = BULLET(&AP);
}
else
printf("delete failed, err: %d\n",rez);
}
LP.func = UNLOCK_DATA_XB;
LP.handle = dataID;
LP.recStart = 0; // unlock all
rez2 = BULLET(&LP);
if (rez==0) rez=rez2;
return(rez);
ΓòÉΓòÉΓòÉ 10.21. Memo Bypass (Memo Create, Open, Close, Read/Flush Header) ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
MEMODATAPACK MDP; // packs used here
// These five routines are normally performed automatically, as described
// in the main documentation, and seldom would need to be called directly.
MDP.func = MEMO_BYPASS_XB;
MDP.dbfHandle = dataID; // handle of DBF
MDP.memoBypass = BypassRoutineToDo;
// where BypassRoutineToDo is one of the following:
//
// BYPASS_CREATE_MEMO
// BYPASS_OPEN_MEMO
// BYPASS_CLOSE_MEMO
// BYPASS_READ_MEMO_HEADER
// BYPASS_FLUSH_MEMO_HEADER
rez = BULLET(&MDP);
if (rez) return(rez);
ΓòÉΓòÉΓòÉ 10.22. Key Access Without Data Record Read ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
CHAR keyBuffer[64]; // enough for the largest possible key
ACCESSPACK AP; // packs used here
// -----------------------------------------------------------
// this section starts at the first in-order key and reads all
// keys in the index file in order
AP.func = FIRST_KEY_XB;
AP.handle = indexID;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
while (rez==0) {
// show first 8 bytes of key, and the record number the key is for
printf("%8.8s %9.9lu\r", keyBuffer, AP.recNo);
AP.func = NEXT_KEY_XB; // and get the next key...
rez = BULLET(&AP); // until all keys accessed
};
if (rez!=EXB_END_OF_FILE) return(rez); // expected rez is EXB_END_OF_FILE
// ----------------------------------------------------------
// this section starts at the last in-order key and reads all
// keys in the index file in reverse order
AP.func = LAST_KEY_XB;
AP.handle = indexID;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
while (rez==0) {
// show first 8 bytes of key, and the record number the key is for
printf("%8.8s %9.9lu\r", keyBuffer, AP.recNo);
AP.func = PREV_KEY_XB; // and get the previous key...
rez = BULLET(&AP); // until all keys accessed
};
if (rez!=EXB_TOP_OF_FILE) return(rez); // expected rez is EXB_TOP_OF_FILE
// this section starts at the first key that starts with "SM", or if
// no keys do, the first key after "SM", and gets that key
// keys in the index file in reverse order
memset(keyBuffer,0,64); // ensure remaining bytes are \0 (required)
strcpy(keyBuffer,"SM"); // get key of "SM", if present (not likely)
AP.func = EQUAL_KEY_XB;
AP.handle = indexID;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
// since "SM" is only being used as a partial key search criterion,
// it won't be found (though, of course, it is a valid key) in this
// example -- however, by using NEXT_KEY_XB, the next key is accessed,
// say, for example, "SMITH"... It could also be "TIMBU" if there were
// no keys with values greater than "SM" and less than "TIMBU".
// Be aware that if another thread in this process (repeat:
// this process!) is accessing this index file (it's not likely that
// you will write your could so that this would happen), then you
// can no longer rely on any multi-call key access to be an atomic
// operation. If you need to have more than one thread access the
// same index (and you require NEXT_KEY_XB or PREV_KEY_XB), then you
// must semaphore protect your code so you don't try to access the same
// index file.
if (rez!=EXB_KEY_NOT_FOUND) { // expected
AP.func = NEXT_KEY_XB; // so get the first key after "SM"
AP.handle = indexID;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
if (rez==0)
printf("The first key >= SM is %s\n",AP.keyBuffer);
}
ΓòÉΓòÉΓòÉ 10.23. Building and Storing Raw Key ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP; // packs used here
// this example shows a simple database insert process
// INSERT_XB should be used instead since it does all this and then some
// files should be locked (not shown)
AP.func = ADD_RECORD_XB;
AP.handle = dataID;
AP.recPtr = &yourRecord;
rez = BULLET(&AP);
if (rez) return(rez);
// AP.recNo is returned by Bullet and will be used later
AP.func = BUILD_KEY_XB;
AP.handle = indexID;
AP.recPtr = &yourRecord;
AP.keyPtr = keyBuffer; // CHAR keyBuffer[64];
rez = BULLET(&AP);
if (rez) return(rez);
// keyBuffer filled with key to store
// a \0\0 enumerator is attached if the index file was created with DUPS_ALLOWED
AP.func = STORE_KEY_XB;
AP.handle = indexID;
// AP.recNo is already set from the ADD_RECORD_XB call
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
if (rez) return(rez);
// if no error, the key was inserted in the index file and the record
// number was associated with that key -- next time you access that
// key, the record number is returned (along with the key itself) --
// and with that record number you access the data file
// when using this routine, you must check the error for a EXB_KEY_EXISTS
// error and if DUPS_ALLOWED, you must manage your own enumerator --
// INSERT_XB is the only routine that does this automatically so unless
// you have a real desire to manage this yourself (among other things)
// use INSERT_XB instead of all this.
ΓòÉΓòÉΓòÉ 10.24. Getting Current Key, Key for Rec/RecNo Pair, Deleting Key ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP;
LOCKPACK LP; // packs used here
AP.func = GET_CURRENT_KEY_XB;
AP.handle = indexID;
AP.keyPtr = keyBuffer; // current key placed here by Bullet
rez = BULLET(&AP);
if (rez) return(rez);
printf("The last accessed key for indexID is in keyBuffer, including any enumerator\n");
// This next example assumes that you are maintaining your own method of
// transaction rollback, and are about to delete the last item added to the
// database: in this case, the last data record is removed from the one
// DBF data file, and the key for that record is removed from the index file
// -- normally, you wouldn't do this, but if you have the need...
rez = InsertToDatabaseHoweverYouDoIt(yourPtr);
// Assume the above called succeeded, but you've decided, for whatever reason,
// that you want to backout the insert... normally, if you used INSERT_XB to
// insert a record/key into a database, you'd already have the record number
// used AND the key used for the record (for each if more than one) -- but for
// this example, assume that only the record number is known. Steps done
// to remove the record and key for this would be:
//
// 1. Exclusive full-lock files
// 2. Get the record data at the record number (GET_RECORD_XB)
// 3. Get the key for this record/recNo pair (GET_KEY_FOR_RECORD_XB)
// 4. Delete the key (DELETE_KEY_XB) (delete the key before deleting the record)
// 5. Delete the record data (DEBUMP_RECORD_XB) (record must be last record in file)
// 6. Unlock files
// lock files being processed
LP.func = LOCK_XB;
LP.handle = indexID; // also locks indexID's owner (its DBF)
LP.xlMode = LOCK_EXCLUSIVE; // exclusive lock for index
LP.dlMode = LOCK_EXCLUSIVE; // exclusive lock for data
LP.nextPtr = NULL; // only one pack
rez = BULLET(&LP);
if (rez) return(LP.stat); // rez for transaction-list is NOT the error
// get actual data record for record number
AP.func = GET_RECORD_XB;
AP.handle = dataID;
AP.recNo = recNoToDelete; // get this record to...
AP.recPtr = &yourRecord; // ...this data record buffer (a structure var)
rez = BULLET(&AP);
if (rez) goto MustAlwaysUnlock;
// now have the data record/data number pair --
// it must be the last physical record in the data file -- if this cannot
// be known, use STAT_DATA_XB to verify that recNoToDelete==SDP.records
AP.func = GET_KEY_FOR_RECORD_XB;
AP.handle = indexID;
AP.recNo = recNoToDelete;
AP.recPtr = &yourRecord;
AP.keyPtr = keyBuffer; // CHAR keyBuffer[64]; key returned here
rez = BULLET(&AP);
if (rez) goto MustAlwaysUnlock;
// now have the key (with any attached enumerator) in keyBuffer, delete it
AP.func = DELETE_KEY_XB;
AP.handle = indexID;
AP.keyPtr = keyBuffer; // was set with key by GET_KEY_FOR_RECORD_XB
rez = BULLET(&AP);
if (rez) goto MustAlwaysUnlock;
// and delete (physically remove) the data record
AP.func = DEBUMP_RECORD_XB;
AP.handle = dataID;
AP.recNo = recNoToDelete;
rez = BULLET(&AP);
// if (rez) goto MustAlwaysUnlock;
MustAlwaysUnlock: // unlock ALWAYS required if lock succeeded
LP.func = UNLOCK_XB;
LP.handle = indexID;
LP.nextPtr = NULL;
rez2 = BULLET(&LP);
if (rez2) rez2=LP.stat; // rez for transaction-list is NOT the error
if (rez==0) rez=rez2;
return(rez);
ΓòÉΓòÉΓòÉ 10.25. Get Data by Key Order ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP; // packs used here
// First example starts at first in-order data and moves through to last
AP.func = GET_FIRST_XB;
AP.handle = indexID;
AP.recPtr = &yourRecord; // struct yourStructure yourRecord;
AP.keyPtr = keyBuffer; // CHAR keyBuffer[64];
rez = BULLET(&AP);
if (rez) return(rez);
printf("The first in-order key is in keyBuffer and its record in yourRecord\n");
AP.func = GET_NEXT_XB; // other parm same as set above
while (rez==0) {
rez = BULLET(&AP);
if (rez) break;
printf("The next in-order key is in keyBuffer and its record in yourRecord\n");
}
if (rez==EXB_END_OF_FILE) rez==0; // expected rez after end of file
if (rez) return(rez);
// Second example starts at last in-order data and moves through to first
// note: since above already is past last, the call to GET_LAST_XB in
// this example would not be necessary -- a call to GET_PREV_XB
// could have been made directly -- however, it doesn't matter
AP.func = GET_LAST_XB;
AP.handle = indexID;
AP.recPtr = &yourRecord;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
if (rez) return(rez);
printf("The last in-order key is in keyBuffer and its record in yourRecord\n");
AP.func = GET_PREV_XB; // other parms same as set above
while (rez==0) {
rez = BULLET(&AP);
if (rez) break;
printf("The previous in-order key is in keyBuffer and its record in yourRecord\n");
}
if (rez==EXB_TOP_OF_FILE) rez==0; // expected rez before at beginning of file
if (rez) return(rez);
// Third example performs a GET_EQUAL_OR_GREATER operation, typically
// used to locate to a key based on a partial search criterion
AP.func = GET_EQUAL_XB;
AP.handle = indexID;
AP.recPtr = &yourRecord; // to be filled on return, if found
memset(keyBuffer,0,sizeof(keyBuffer); // clear it out (required)
strcpy(keyBuffer,"KING"); // find first key starting with 'KING'
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
if (rez==0)
printf("Matched search key in keyBuffer EXACTLY -- its record is in yourRecord\n");
else if (rez==EXB_KEY_NOT_FOUND) {
// since not found, get the following in-order one (say, 'KINGSTON')
AP.func = GET_NEXT_XB;
rez = BULLET(&AP);
if (rez) return(rez);
printf("Not exact, but next greater key is in keyBuffer and its record is in yourRecord\n");
}
else
return(rez);
ΓòÉΓòÉΓòÉ 10.26. Insert Data Record with Key Into Database ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP;
LOCKPACK LP; // packs used here
LP.func = LOCK_XB;
LP.handle = indexID; // also locks indexID's owner (its DBF)
LP.xlMode = LOCK_EXCLUSIVE; // exclusive lock for index
LP.dlMode = LOCK_EXCLUSIVE; // exclusive lock for data
LP.nextPtr = NULL; // only one pack
rez = BULLET(&LP);
if (rez) return(LP.stat); // rez for transaction-list is NOT the error
AP.func = INSERT_XB;
AP.handle = indexID;
AP.recNo = 0; // must be zero
AP.recPtr = &yourRecord; // contains data record
AP.keyPtr = keyBuffer; // empty, on return has key stored
AP.nextPtr = NULL; // only the single pack
rez = BULLET(&AP);
// on return, as on all transaction-list routines, rez is not the return
// code but is the pack item that failed (neg if data, pos if index)
if (rez==0)
printf("okay\n");
else if (rez < 0)
printf("insert failed with data, err: %d\n",AP.stat);
else
printf("insert failed with index, err: %d\n",AP.stat);
// if locked, MUST unlock!
LP.func = UNLOCK_XB;
LP.handle = indexID;
LP.nextPtr = NULL;
rez2 = BULLET(&LP);
if (rez2) rez2=LP.stat; // rez for transaction-list is NOT the error
if (rez==0) rez=rez2 // return the most interesting error, if any
ΓòÉΓòÉΓòÉ 10.27. Update Data Record with Key Update ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
ACCESSPACK AP;
LOCKPACK LP; // packs used here
LP.func = LOCK_XB;
LP.handle = indexID; // also locks indexID's owner (its DBF)
LP.xlMode = LOCK_SHARED; // shared lock for index
LP.dlMode = LOCK_SHARED; // shared lock for data
LP.nextPtr = NULL; // only one pack
rez = BULLET(&LP);
if (rez) return(LP.stat); // rez for transaction-list is NOT the error
AP.func = GET_FIRST_XB; // get first key's data record (and its recNo)
AP.handle = indexID;
AP.recPtr = &yourRecord;
AP.keyPtr = keyBuffer;
rez = BULLET(&AP);
if (rez==0) {
// assume that we want to change this entry now (as if we weren't sure
// that we wanted to initially, hence the initial shared lock) --
// this requires write access so relock to allow write access
LP.func = RELOCK_XB;
LP.handle = indexID;
LP.xlMode = LOCK_EXCLUSIVE; // exclusive lock for index
LP.dlMode = LOCK_EXCLUSIVE; // exclusive lock for data
LP.recStart = 0;
LP.nextPtr = NULL;
rez = BULLET(&LP);
if (rez) rez=LP.stat // xaction-list routine so use LP.stat
if (rez==0) {
strcpy(yourRecord.someField,"new field data");
// AP.recNo has been set by Bullet GET_XB call above
AP.func = UPDATE_XB;
AP.nextPtr = NULL; // all other AP members set above
rez = BULLET(&AP);
// on return, as on all transaction-list routines, rez is not the return
// code but is the pack item that failed (neg if data, pos if index)
if (rez==0)
printf("okay\n");
else if (rez < 0)
printf("update failed with data, err: %d\n",AP.stat);
else
printf("update failed with index, err: %d\n",AP.stat);
}
else
printf("relock failed, rez: %d err: %d\n",rez,LP.stat);
}
// if locked, MUST unlock!
LP.func = UNLOCK_XB;
LP.handle = indexID;
LP.nextPtr = NULL;
rez2 = BULLET(&LP);
if (rez2) rez2=LP.stat; // rez for transaction-list is NOT the error
if (rez==0) rez=rez2 // return the most interesting error, if any
ΓòÉΓòÉΓòÉ 10.28. Remote Drive, File/Device Check ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
REMOTEPACK RP; // packs used here
// check if file in handle (or device handle) is on a 'network' drive
RP.func = CHECK_REMOTE_XB;
RP.handle = indexID; // check if indexID handle is on a network
rez = BULLET(&RP);
if (rez) return(rez);
if (RP.isRemote)
printf("handle is on a network drive\n");
// check if drive is a 'network' drive
RP.func = CHECK_REMOTE_XB;
RP.handle = 0; // set RP.handle to 0 to check drive
RP.drive = 0; // check if current drive is network drive
rez = BULLET(&RP); // to check drive C:, set RP.drive=3
if (rez) return(rez); // D: is RP.drive=4, and so on
if (RP.isRemote)
printf("current drive is a network drive\n");
ΓòÉΓòÉΓòÉ 10.29. Relock Individual File ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
LOCKPACK LP;
STATHANDLEPACK SHP;
STATDATAPACK SDP;
STATINDEXPACK SIP; // packs used here
// given a handle, determine if it's DBF or index
// check if currently locked
// if locked, determine if shared or exclusive lock
// if exclusive lock, relock to shared
// if not locked, do nothing
SHP.func = STAT_HANDLE_XB;
SHP.handle = passedHandle;
rez = BULLET(&SHP);
if (SHP.ID==-1)
puts("Handle is not a Bullet data or index file\n");
else {
if (SHP.ID==0) {
// normally, you lock before calling this routine but since
// SIP.lockCount and SIP.flags are all that is being checked,
// and since nothing is to be done if the handle is not locked,
// it's okay in this instance to use this routine without
// first explicitly locking the handle
SIP.func = STAT_INDEX_XB;
SIP.handle = passedHandle;
rez = BULLET(&SIP);
if (rez) goto ErrorHandler;
// if locked, check if the lock is exclusive
if (SIP.lockCount) { // count of active full locks
if ((SIP.flags & 4)==0) { // bit2=0 means lock is not shared
// currently exclusive, make it shared
LP.func = RELOCK_INDEX_XB;
LP.handle = passedHandle;
LP.xlMode = LOCK_SHARED;
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
}
}
}
else {
SDP.func = STAT_DATA_XB;
SDP.handle = passedHandle;
rez = BULLET(&SDP);
if (rez) goto ErrorHandler;
if (SDP.lockCount) { // count of active full locks
if ((SDP.flags & 4)==0) { // bit2=0 means lock is not shared
// currently exclusive, make it shared
LP.func = RELOCK_DATA_XB;
LP.handle = passedHandle;
LP.dlMode = LOCK_SHARED;
LP.startRec = 0; // entire file
rez = BULLET(&LP);
if (rez) goto ErrorHandler;
}
}
}
}
ΓòÉΓòÉΓòÉ 10.30. DOS Disk Routines Through Bullet ΓòÉΓòÉΓòÉ
#include "bullet_2.h"
DOSFILEPACK DFP; // packs used here
CHAR dataDirname[] = "under";
CHAR dataFilename[]= "under\\data.dbf";
CHAR newFilename[] = "under\\newdata.dbf";
// check if pathame can be access for read/write denynone (expected not here)
DFP.func = ACCESS_FILE_DOS;
DFP.filenamePtr = dataFilename;
DFP.asMode = 0x42;
rez = BULLET(&DFP);
if (rez==0) return(0); // file already exists
if (rez==5) return(5); // access denied (exists but in use)
// other errors possible, too, do the Make and Create and go by those results
// make directory for file
DFP.func = MAKE_DIR_DOS;
DFP.filenamePtr = dataDirname;
rez = BULLET(&DFP);
if (rez) rez=0; // can't create subdirectory (already exists?)
// create file
DFP.func = CREATE_FILE_DOS;
DFP.filenamePtr = dataFilename;
DFP.attr = 0; // 'normal' attributes
rez = BULLET(&DFP);
if (rez) return(rez); // can't create file
// open file
DFP.func = OPEN_FILE_DOS;
DFP.filenamePtr = dataFilename;
DFP.asMode = 0x42;
rez = BULLET(&DFP);
if (rez) return(rez); // can't open file
// DFP.handle is set by OPEN_FILE_DOS call above
// pre-allocate file space
DFP.func = EXPAND_FILE_DOS;
// DFP.handle already set
// DFP.asMode already set
DFP.bytes = 128*1024; // set filesize to 128KB
rez = BULLET(&DFP);
if (rez) return(rez); // can't do it (close file before return)
CHAR writeStuff[]="Write this string to offset 888";
DFP.func = SEEK_FILE_DOS;
// DFP.handle already set
DFP.seekTo = 888;
DFP.method = 0; // from start of file
rez = BULLET(&DFP);
if (rez) return(rez); // can't do it (close...)
// write the string to disk
DFP.func = WRITE_FILE_DOS;
// DFP.handle already set
DFP.bytes = strlen(writeStuff);
DFP.bufferPtr = writeStuff;
rez = BULLET(&DFP);
if (rez) return(rez);
// commit to the deep
DFP.func = COMMIT_FILE_DOS;
// DFP.handle already set
rez = BULLET(&DFP);
if (rez) return(rez);
// reposition to where write started so can read what was written
DFP.func = SEEK_FILE_DOS;
// DFP.handle already set
DFP.seekTo = 888;
DFP.method = 0;
rez = BULLET(&DFP);
if (rez) return(rez);
CHAR readBuffer[128];
DFP.func = READ_FILE_DOS;
// DFP.handle already set
DFP.bytes = strlen(writeStuff); // read it back
DFP.bufferPtr = readBuffer;
rez = BULLET(&DFP);
if (rez) return(rez);
if (DFP.bytes != strlen(writeStuff))
printf("read came up short!\n");
DFP.func = CLOSE_FILE_DOS;
// DFP.handle already set
rez = BULLET(&DFP);
if (rez) return(rez);
DFP.handle = 0; // it's gone (closed, anyway)
// rename the file
DFP.func = RENAME_FILE_DOS;
DFP.filenamePtr = dataFilename;
DFP.newFilenamePtr = newFilename;
rez = BULLET(&DFP);
if (rez) return(rez);
// and get rid of it (known now as newFilename)
DFP.func = DELETE_FILE_DOS;
DFP.filenamePtr = newFilename;
rez = BULLET(&DFP);
return(rez);
// Why the DosXXX routines when they're standard API? Not all
// language tools are able to directly call the API, but can
// call them indirectly through the Bullet DLL.
ΓòÉΓòÉΓòÉ 11. Bullet Errors ΓòÉΓòÉΓòÉ
Bullet error codes are numbered so that they do not overlap OS error codes.
The first general Bullet error number is 8193, but Bullet also returns select
system error code numbers instead of duplicating system codes (those listed
below less than 8192). In addition, if the error is reported by the OS (for
example, attempting to access a locked file), then the OS error in returned by
Bullet. For a list of OS errors, see OS/2 Dos API Errors.
System Error Codes
8 EXB_NOT_ENOUGH_MEMORY
cannot get memory requested
15 EXB_INVALID_DRIVE
not a valid drive letter
38 EXB_UNEXPECTED_EOF
unexpected end-of-file where bytes requested for read exceeded EOF
39 EXB_DISK_FULL
disk full on WriteFile
80 EXB_FILE_EXISTS
cannot create file since it already exists
105 EXB_SEM_OWNER_DIED
owner of mutex semaphore died (used in place of Win32 80h)
640 EXB_TIMEOUT
mutex semaphore timed out (used in place of Win32 102h)
8192+ EXB_OR_WITH_FAULTS
1=flush failed on handle close
2=free memory failed on handle close
4=failed memo handle close (data handle only)
During a CLOSE_XB routine, the close process continues
regardless of errors, and so the errors are accumulated.
For example, 8193 means the flush failed, and 8195 means
both the flush and the free failed (8192+1+2=8195).
If the error occurred in the actual DosClose() API call,
only that error is returned (it will be an OS error code).
8251 EXB_216501
INT21/6501h not supported by DOS extender (see ccdosfn.c)
8256 EXB_216506
INT21/6506h not supported by DOS extender (see ccdosfn.c)
8300 EXB_ILLEGAL_CMD
function not allowed
8301 EXB_OLD_DOS
OS version < MIN_DOS_NEEDED
8302 EXB_NOT_INITIALIZED
init not active, must do INIT_XB before using Bullet
8303 EXB_ALREADY_INITIALIZED
init already active, must do EXIT_XB first
8304 EXB_TOO_MANY_HANDLES
more than 1024 opens requested,
or more than license permits (100, 250, 1024)
8305 EXB_SYSTEM_HANDLE
Bullet won't use or close handles 0-2
8306 EXB_FILE_NOT_OPEN
the handle is not a Bullet handle, including the
handle supplied in OP.xbLink
8307 EXB_FILE_IS_DIRTY
tried to reload header but current still dirty;
flush the file before reloading the header
8308 EXB_BAD_FILETYPE
attempted to do a key file operation on non-key file,
or a data operation on a non-data file
8309 EXB_TOO_MANY_PACKS
too many INSERT, UPDATE, REINDEX, LOCK_XB packs (more than 256)
8310 EXB_NULL_RECPTR
null record pointer passed to Bullet (.recPtr==NULL)
8311 EXB_NULL_KEYPTR
null key pointer passed to Bullet (.keyPtr==NULL)
8312 EXB_NULL_MEMOPTR
null memo pointer passed to Bullet (.memoPtr==NULL)
8313 EXB_EVALUATION_LIMIT
evaluation limit of 300 records exceeded (remedy: buy a license)
8314 EXB_BAD_INDEX
Query/SetSysVars index selection is beyond the last one
8315 EXB_RO_INDEX
SetSysVars index item is read-only
8316 EXB_FILE_BOUNDS
file size > 4GB, or greater than the SetSysVars value
8397 EXB_FORCE_ROLLBACK
rollback test completed (last AP[].nextPtr=-1 for Insert/Update) (test-use only)
8398 EXB_INVALID_DLL
DLL seems to be invalid (8399 similar)
Multi-access Error Codes
8401 EXB_BAD_LOCK_MODE
lock mode (LP) not valid, must be 0 or 1
8402 EXB_NOTHING_TO_RELOCK
cannot relock without existing full-lock
8403 EXB_SHARED_LOCK_ON
unlikely error, write access needed for flush, but lock is shared
Index Error Codes
8501 EXB_KEY_NOT_FOUND
exact match of key not found
8502 EXB_KEY_EXISTS
key exists already and dups not allowed
8503 EXB_END_OF_FILE
already at last index order
8504 EXB_TOP_OF_FILE
already at first index order
8505 EXB_EMPTY_FILE
nothing to do since no keys
8506 EXB_CANNOT_GET_LAST
cannot locate last key
8507 EXB_BAD_INDEX_STACK
index file is corrupt
8508 EXB_BAD_INDEX_READ0
index file is corrupt
8509 EXB_BAD_INDEX_WRITE0
index file is corrupt
8521 EXB_OLD_INDEX
incompatible Bullet index, use ReindexOld subroutine, if available
8522 EXB_UNKNOWN_INDEX
not a Bullet index file
8523 EXB_KEY_TOO_LONG
keylength > 62 (or 64 if unique), or is 0
8531 EXB_PARSER_NULL
parser function pointer is NULL
8532 EXB_BUILDER_NULL
build key function pointer is NULL
8533 EXB_BAD_SORT_FUNC
CIP.sortFunction not valid (not 1-6, or a custom sort-compare)
8534 EXB_BAD_NODE_SIZE
CIP.nodeSize is not 512, 1024, or 2048
8535 EXB_FILENAME_TOO_LONG
CIP.filenamePtr->pathname greater than file system allows
This error is detected only for the file system installed,
and does not detect using pathnames greater than 80 on a FAT
system if HPFS is installed. The OS returns its own error
in this case, after the fact.
8541 EXB_KEYX_NULL
key expression is effectively NULL
8542 EXB_KEYX_TOO_LONG
CIP.keyExpPtr->expression is greater than 159 bytes
8543 EXB_KEYX_SYM_TOO_LONG
fieldname/funcname in expression is longer than 10 chars
8544 EXB_KEYX_SYM_UNKNOWN
fieldname/funcname in expression is unknown or misspelled
8545 EXB_KEYX_TOO_MANY_SYMS
too many symbols/fields used in expression (16 max)
8546 EXB_KEYX_BAD_SUBSTR
invalid SUBSTR() operand in expression
8547 EXB_KEYX_BAD_SUBSTR_SZ
SUBSTR() exceeds field's size
8548 EXB_KEYX_BAD_FORM
didn't match expected symbol in expression (missing paren, etc.)
8551 EXB_NO_READS_FOR_RUN
unlikely error, use different reindex buffer size to fix
8552 EXB_TOO_MANY_RUNS
unlikely error, too many runs (64K or more runs)
8553 EXB_TOO_MANY_RUNS_FOR_BUFFER
unlikely error, too many runs for run buffer
8554 EXB_TOO_MANY_DUPLICATES
more than 64K "identical" keys since the last enumerator used
was 0xFFFF -- if ever you have this error, REINDEX_XB should
be used to resequence the enumerators
8561 EXB_INSERT_RECNO_BAD
AP.recNo cannot be > 0 if inserting with INSERT_XB
8562 EXB_PREV_APPEND_EMPTY
no previous append for INSERT_XB yet AP.recNo==0x80000000
8563 EXB_PREV_APPEND_MISMATCH
previous append's xbLink does not match this
-- if this pack's AP.recNo=0x80000000 then this pack's AP.handle
must be the same handle as that of the last pack that added a record
8564 EXB_INSERT_KBO_FAILED
could not back out key at INSERT_XB
8565 EXB_INSERT_DBO_FAILED
could not back out data records at INSERT_XB
8571 WRN_NOTHING_TO_UPDATE
all AP.recNo=0 at UPDATE_XB so nothing to do
8572 EXB_INTERNAL_UPDATE
internal error UPDATE_XB, not in handle/record# list
8573 EXB_FAILED_DATA_RESTORE
could not restore original data record (*)
8574 EXB_FAILED_KEY_DELETE
could not remove new key (*)
8575 EXB_FAILED_KEY_RESTORE
could not restore original key(*)
(*) original error, which forced a back-out, has been
replaced by this error -- this error is always returned
in the first AP.stat (-1 on data, 1 on index)
Data Error Codes
8601 EXB_EXT_XBLINK
xbLink handle is not an internal DBF, as was specified during the
index file's creation -- the Bullet routine called requires a Bullet
DBF data file (instead use index-only access methods like NEXT_KEY_XB).
8602 EXB_FIELDNAME_TOO_LONG
fieldname is > 10 characters
8603 EXB_RECORD_TOO_LONG
record length is > 64K
8604 EXB_FIELD_NOT_FOUND
fieldname not found in descriptor info
8605 EXB_BAD_FIELD_COUNT
fields <= 0 or >= MAX_FIELDS; also use of a field number which
is beyond the last field
8606 EXB_BAD_HEADER
bad header (reclen=0, etc.)
8607 EXB_BUFFER_TOO_SMALL
buffer too small (pack buffer < record length)
8608 EXB_INTERNAL_PACK
internal error in PackRecords
8609 EXB_BAD_RECNO
record number=0 or > records in data file header, or
pack attempt on empty data file
8610 WRN_RECORD_TAGGED
record's tag field matches skip tag
Memo Error Codes
8701 WRN_CANNOT_OPEN_MEMO
the DBF header has bits 3 & 7 set, which indicates that a memo
file is attached to this DBF, but the DBT memo file failed to open
-- the DBF open continues, with this warning code returned
8702 EXB_MEMO_NOT_OPEN
no open memo file for operation
8703 EXB_BAD_BLOCKSIZE
memo blocksize must be at least 24 bytes
8704 EXB_MEMO_DELETED
memo is deleted
8705 EXB_MEMO_PAST_END
memo data requested is past end of record
8706 EXB_BAD_MEMONO
memo number is not valid
8707 EXB_MEMO_IN_USE
memo add encountered likely corrupt memo file
-- avail list indicates this memo record is deleted, but the memoAvail
link for the memo indicates it is use (memoAvail link==0x8FFFF)
8708 EXB_BAD_AVAIL_LINK
memo avail link cannot be valid (e.g., memoAvail==0)
8709 EXB_MEMO_ZERO_SIZE
memo data has no size (size is 0)
8710 EXB_MEMO_IS_SMALLER
memo attempt to shrink but memo size is already <= size requested
ΓòÉΓòÉΓòÉ 12. OS/2 Dos API Errors ΓòÉΓòÉΓòÉ
0 NO_ERROR
No error occurred.
1 ERROR_INVALID_FUNCTION
Invalid function number.
2 ERROR_FILE_NOT_FOUND
File not found.
3 ERROR_PATH_NOT_FOUND
Path not found.
4 ERROR_TOO_MANY_OPEN_FILES
Too many open files (no handles left).
5 ERROR_ACCESS_DENIED
Access denied.
6 ERROR_INVALID_HANDLE
Invalid handle.
7 ERROR_ARENA_TRASHED
Memory control blocks destroyed.
8 ERROR_NOT_ENOUGH_MEMORY
Insufficient memory.
9 ERROR_INVALID_BLOCK
Invalid memory-block address.
10 ERROR_BAD_ENVIRONMENT
Invalid environment.
11 ERROR_BAD_FORMAT
Invalid format.
12 ERROR_INVALID_ACCESS
Invalid access code.
13 ERROR_INVALID_DATA
Invalid data.
14 Reserved.
15 ERROR_INVALID_DRIVE
Invalid drive specified.
16 ERROR_CURRENT_DIRECTORY
Attempting to remove current directory.
17 ERROR_NOT_SAME_DEVICE
Not same device.
18 ERROR_NO_MORE_FILES
No more files.
19 ERROR_WRITE_PROTECT
Attempt to write on write-protected diskette.
20 ERROR_BAD_UNIT
Unknown unit.
21 ERROR_NOT_READY
Drive not ready.
22 ERROR_BAD_COMMAND
Unknown command.
23 ERROR_CRC
Data error - cyclic redundancy check.
24 ERROR_BAD_LENGTH
Invalid request structure length.
25 ERROR_SEEK
Seek error.
26 ERROR_NOT_DOS_DISK
Unknown media type.
27 ERROR_SECTOR_NOT_FOUND
Sector not found.
28 ERROR_OUT_OF_PAPER
Printer is out of paper.
29 ERROR_WRITE FAULT
Write fault.
30 ERROR_READ_FAULT
Read fault.
31 ERROR_GEN_FAILURE
General failure.
32 ERROR_SHARING_VIOLATION
Sharing violation.
33 ERROR_LOCK_VIOLATION
Lock violation.
34 ERROR_WRONG_DISK
Invalid disk change.
35 ERROR_FCB_UNAVAILABLE
FCB unavailable.
36 ERROR_SHARING_BUFFER_EXCEEDED
Sharing buffer overflow.
37 ERROR_CODE_PAGE_MISMATCHED
Code page does not match.
38 ERROR_HANDLE_EOF
End of file reached.
39 ERROR_HANDLE_DISK_FULL
Disk is full.
40-49 Reserved.
50 ERROR_NOT_SUPPORTED
Network request not supported.
51 ERROR_REM_NOT_LIST
Remote network node is not online.
52 ERROR_DUP_NAME
Duplicate file name in network.
53 ERROR_BAD_NETPATH
Network path not found.
54 ERROR_NETWORK_BUSY
Network is busy.
55 ERROR_DEV_NOT_EXIST
Device is not installed in network.
56 ERROR_TOO_MANY_CMDS
Network command limit reached.
57 ERROR_ADAP_HDW_ERR
Network adapter hardware error.
58 ERROR_BAD_NET_RESP
Incorrect response in network.
59 ERROR_UNEXP_NET_ERR
Unexpected error in network.
60 ERROR_BAD_REM_ADAP
Remote network adapter error.
61 ERROR_PRINTQ_FULL
Network printer queue is full.
62 ERROR_NO_SPOOL_SPACE
No space in print spool file.
63 ERROR_PRINT_CANCELLED
Print spool file deleted.
64 ERROR_NETNAME_DELETED
Network name deleted.
65 ERROR_NETWORK_ACCESS_DENIED
Access to network denied.
66 ERROR_BAD_DEV_TYPE
Device type invalid for network.
67 ERROR_BAD_NET_NAME
Network name not found.
68 ERROR_TOO_MANY_NAMES
Network name limit exceeded.
69 ERROR_TOO_MANY_SESS
Network session limit exceeded.
70 ERROR_SHARING_PAUSED
Temporary pause in network.
71 ERROR_REQ_NOT_ACCEP
Network request denied.
72 ERROR_REDIR_PAUSED
Pause in network print disk redirection.
73 ERROR_SBCS_ATT_WRITE_PROT
Attempted write on protected disk.
74 ERROR_SBCS_GENERAL_FAILURE
General failure, single-byte character set.
75-79 Reserved.
80 ERROR_FILE_EXISTS
File exists.
81 ERROR_DUP_FCB
Reserved.
82 ERROR_CANNOT_MAKE
Cannot make directory entry.
83 ERROR_FAIL_I24
Failure on INT 24.
84 ERROR_OUT_OF_STRUCTURES
Too many redirections.
85 ERROR_ALREADY_ASSIGNED
Duplicate redirection.
86 ERROR_INVALID_PASSWORD
Invalid password.
87 ERROR_INVALID_PARAMETER
Invalid parameter.
88 ERROR_NET_WRITE_FAULT
Network device fault.
89 ERROR_NO_PROC_SLOTS
No process slots available.
90 ERROR_NOT_FROZEN
System error.
91 ERR_TSTOVFL
Timer service table overflow.
92 ERR_TSTDUP
Timer service table duplicate.
93 ERROR_NO_ITEMS
No items to work on.
95 ERROR_INTERRUPT
Interrupted system call.
99 ERROR_DEVICE_IN_USE
Device in use.
100 ERROR_TOO_MANY_SEMAPHORES
User/system open semaphore limit reached.
101 ERROR_EXCL_SEM_ALREADY_OWNED
Exclusive semaphore already owned.
102 ERROR_SEM_IS_SET
DosCloseSem found semaphore set.
103 ERROR_TOO_MANY_SEM_REQUESTS
Too many exclusive semaphore requests.
104 ERROR_INVALID_AT_INTERRUPT_TIME
Operation invalid at interrupt time.
105 ERROR_SEM_OWNER_DIED
Previous semaphore owner terminated without freeing semaphore.
106 ERROR_SEM_USER_LIMIT
Semaphore limit exceeded.
107 ERROR_DISK_CHANGE
Insert drive B disk into drive A.
108 ERROR_DRIVE_LOCKED
Drive locked by another process.
109 ERROR_BROKEN_PIPE
Write on pipe with no reader.
110 ERROR_OPEN_FAILED
Open/create failed due to explicit fail command.
111 ERROR_BUFFER_OVERFLOW
Buffer passed to system call too small to hold return data.
112 ERROR_DISK_FULL
Not enough space on the disk.
113 ERROR_NO_MORE_SEARCH_HANDLES
Cannot allocate another search structure and handle.
114 ERROR_INVALID_TARGET_HANDLE
Target handle in DosDupHandle invalid.
115 ERROR_PROTECTION_VIOLATION
Invalid user virtual address.
116 ERROR_VIOKBD_REQUEST
Error on display write or keyboard read.
117 ERROR_INVALID_CATEGORY
Category for DevIOCtl not defined.
118 ERROR_INVALID_VERIFY_SWITCH
Invalid value passed for verify flag.
119 ERROR_BAD_DRIVER_LEVEL
Level four driver not found.
120 ERROR_CALL_NOT_IMPLEMENTED
Invalid function called.
121 ERROR_SEM_TIMEOUT
Time-out occurred from semaphore API function.
122 ERROR_INSUFFICIENT_BUFFER
Data buffer too small.
123 ERROR_INVALID_NAME
Illegal character or invalid file-system name.
124 ERROR_INVALID_LEVEL
Non-implemented level for information retrieval or setting.
125 ERROR_NO_VOLUME_LABEL
No volume label found with DosQueryFSInfo function.
126 ERROR_MOD_NOT_FOUND
Module handle not found with DosQueryProcAddr(),
DosQueryModAddr().
127 ERROR_PROC_NOT_FOUND
Procedure address not found with DosQueryProcAddr().
128 ERROR_WAIT_NO_CHILDREN
DosWaitChild finds no children.
129 ERROR_CHILD_NOT_COMPLETE
DosWaitChild children not terminated.
130 ERROR_DIRECT_ACCESS_HANDLE
Handle operation invalid for direct disk-access handles.
131 ERROR_NEGATIVE_SEEK
Attempting seek to negative offset.
132 ERROR_SEEK_ON_DEVICE
Application trying to seek on device or pipe.
133 ERROR_IS_JOIN_TARGET
Drive has previously joined drives.
134 ERROR_IS_JOINED
Drive is already joined.
135 ERROR_IS_SUBSTED
Drive is already substituted.
136 ERROR_NOT_JOINED
Cannot delete drive that is not joined.
137 ERROR_NOT_SUBSTED
Cannot delete drive that is not substituted.
138 ERROR_JOIN_TO_JOIN
Cannot join to a joined drive.
139 ERROR_SUBST_TO_SUBST
Cannot substitute to a substituted drive.
140 ERROR_JOIN_TO_SUBST
Cannot join to a substituted drive.
141 ERROR_SUBST_TO_JOIN
Cannot substitute to a joined drive.
142 ERROR_BUSY_DRIVE
Specified drive is busy.
143 ERROR_SAME_DRIVE
Cannot join or substitute a drive to a directory on the same drive.
144 ERROR_DIR_NOT_ROOT
Directory must be a subdirectory of the root.
145 ERROR_DIR_NOT_EMPTY
Directory must be empty to use join command.
146 ERROR_IS_SUBST_PATH
Path specified is being used in a substitute.
147 ERROR_IS_JOIN_PATH
Path specified is being used in a join.
148 ERROR_PATH_BUSY
Path specified is being used by another process.
149 ERROR_IS_SUBST_TARGET
Cannot join or substitute a drive that has a directory that is the
target of a previous substitute.
150 ERROR_SYSTEM_TRACE
System trace error.
151 ERROR_INVALID_EVENT_COUNT
DosWaitMuxWaitSem errors.
152 ERROR_TOO_MANY_MUXWAITERS
System limit of 100 entries reached.
153 ERROR_INVALID_LIST_FORMAT
Invalid list format.
154 ERROR_LABEL_TOO_LONG
Volume label too big.
155 ERROR_TOO_MANY_TCBS
Cannot create another TCB.
156 ERROR_SIGNAL_REFUSED
Signal refused.
157 ERROR_DISCARDED
Segment is discarded.
158 ERROR_NOT_LOCKED
Segment is not locked.
159 ERROR_BAD_THREADID_ADDR
Invalid thread-identity address.
160 ERROR_BAD_ARGUMENTS
Invalid environment pointer.
161 ERROR_BAD_PATHNAME
Invalid path name passed to exec.
162 ERROR_SIGNAL_PENDING
Signal already pending.
163 ERROR_UNCERTAIN_MEDIA
Error with INT 24 mapping.
164 ERROR_MAX_THRDS_REACHED
No more process slots.
165 ERROR_MONITORS_NOT_SUPPORTED
Error with INT 24 mapping.
166 ERROR_UNC_DRIVER_NOT_INSTALLED
Default redirection return code.
167 ERROR_LOCK_FAILED
Locking failed.
168 ERROR_SWAPIO_FAILED
Swap I/O failed.
169 ERROR_SWAPIN_FAILED
Swap in failed.
170 ERROR_BUSY
Segment is busy.
171-172 Reserved.
173 ERROR_CANCEL_VIOLATION
A lock request is not outstanding for the specified file range, or the
range length is zero.
174 ERROR_ATOMIC_LOCK_NOT_SUPPORTED
The file-system driver (FSD) does not support atomic lock operations.
Versions of OS/2 prior to version 2.00 do not support atomic lock
operations.
175 ERROR_READ_LOCKS_NOT_SUPPORTED
The file system driver (FSD) does not support shared read locks.
176-179 Reserved.
180 ERROR_INVALID_SEGMENT_NUMBER
Invalid segment number.
181 ERROR_INVALID_CALLGATE
Invalid call gate.
182 ERROR_INVALID_ORDINAL
Invalid ordinal.
183 ERROR_ALREADY_EXISTS
Shared segment already exists.
184 ERROR_NO_CHILD_PROCESS
No child process to wait for.
185 ERROR_CHILD_ALIVE_NOWAIT
NoWait specified and child alive.
186 ERROR_INVALID_FLAG_NUMBER
Invalid flag number.
187 ERROR_SEM_NOT_FOUND
Semaphore does not exist.
188 ERROR_INVALID_STARTING_CODESEG
Invalid starting code segment, incorrect END (label) directive.
189 ERROR_INVALID_STACKSEG
Invalid stack segment.
190 ERROR_INVALID_MODULETYPE
Invalid module type - dynamic-link library file cannot be used as an
application. Application cannot be used as a dynamic-link library.
191 ERROR_INVALID_EXE_SIGNATURE
Invalid EXE signature - file is a DOS mode program or an improper
program.
192 ERROR_EXE_MARKED_INVALID
EXE marked invalid - link detected errors when the application was
created.
193 ERROR_BAD_EXE_FORMAT
Invalid EXE format - file is a DOS mode program or an improper
program.
194 ERROR_ITERATED_DATA_EXCEEDS_64k
Iterated data exceeds 64KB - there is more than 64KB of data in one
of the segments of the file.
195 ERROR_INVALID_MINALLOCSIZE
Invalid minimum allocation size - the size is specified to be less than
the size of the segment data in the file.
196 ERROR_DYNLINK_FROM_INVALID_RING
Dynamic link from invalid privilege level - privilege level 2 routine
cannot link to dynamic-link libraries.
197 ERROR_IOPL_NOT_ENABLED
IOPL not enabled - IOPL set to NO in CONFIG.SYS.
198 ERROR_INVALID_SEGDPL
Invalid segment descriptor privilege level - can only have privilege
levels of 2 and 3.
199 ERROR_AUTODATASEG_EXCEEDS_64k
Automatic data segment exceeds 64KB.
200 ERROR_RING2SEG_MUST_BE_MOVABLE
Privilege level 2 segment must be movable.
201 ERROR_RELOC_CHAIN_XEEDS_SEGLIM
Relocation chain exceeds segment limit.
202 ERROR_INFLOOP_IN_RELOC_CHAIN
Infinite loop in relocation chain segment.
203 ERROR_ENVVAR_NOT_FOUND
Environment variable not found.
204 ERROR_NOT_CURRENT_CTRY
Not current country.
205 ERROR_NO_SIGNAL_SENT
No signal sent - no process in the command subtree has a signal
handler.
206 ERROR_FILENAME_EXCED_RANGE
File name or extension is greater than 8.3 characters.
207 ERROR_RING2_STACK_IN_USE
Privilege level 2 stack is in use.
208 ERROR_META_EXPANSION_TOO_LONG
Meta (global) expansion is too long.
209 ERROR_INVALID_SIGNAL_NUMBER
Invalid signal number.
210 ERROR_THREAD_1_INACTIVE
Inactive thread.
211 ERROR_INFO_NOT_AVAIL
File system information is not available for this file.
212 ERROR_LOCKED
Locked error.
213 ERROR_BAD_DYNALINK
Attempted to execute a non-family API in DOS mode.
214 ERROR_TOO_MANY_MODULES
Too many modules.
215 ERROR_NESTING_NOT_ALLOWED
Nesting is not allowed.
217 ERROR_ZOMBIE_PROCESS
Zombie process.
218 ERROR_STACK_IN_HIGH_MEMORY
Stack is in high memory.
219 ERROR_INVALID_EXITROUTINE_RING
Invalid exit routine ring.
220 ERROR_GETBUF_FAILED
Get buffer failed.
221 ERROR_FLUSHBUF_FAILED
Flush buffer failed.
222 ERROR_TRANSFER_TOO_LONG
Transfer is too long.
224 ERROR_SMG_NO_TARGET_WINDOW
The application window was created without the FCF_TASKLIST
style, or the application window not yet been created or has already
been destroyed.
228 ERROR_NO_CHILDREN
No child process.
229 ERROR_INVALID_SCREEN_GROUP
Invalid session.
230 ERROR_BAD_PIPE
Non-existent pipe or invalid operation.
231 ERROR_PIPE_BUSY
Pipe is busy.
232 ERROR_NO_DATA
No data available on non-blocking read.
233 ERROR_PIPE_NOT_CONNECTED
Pipe was disconnected by server.
234 ERROR_MORE_DATA
More data is available.
240 ERROR_VC_DISCONNECTED
Session was dropped due to errors.
250 ERROR_CIRCULARITY_REQUESTED
Renaming a directory that would cause a circularity problem.
251 ERROR_DIRECTORY_IN_CDS
Renaming a directory that is in use.
252 ERROR_INVALID_FSD_NAME
Trying to access nonexistent FSD.
253 ERROR_INVALID_PATH
Invalid pseudo device.
254 ERROR_INVALID_EA_NAME
Invalid character in name, or invalid cbName.
255 ERROR_EA_LIST_INCONSISTENT
List does not match its size, or there are invalid EAs in the list.
256 ERROR_EA_LIST_TOO_LONG
FEAList is longer than 64K-1 bytes.
257 ERROR_NO_META_MATCH
String does not match expression.
259 ERROR_NO_MORE_ITEMS
DosQueryFSAttach ordinal query.
260 ERROR_SEARCH_STRUC_REUSED
DOS mode findfirst/next search structure reused.
261 ERROR_CHAR_NOT_FOUND
Character not found.
262 ERROR_TOO_MUCH_STACK
Stack request exceeds system limit.
263 ERROR_INVALID_ATTR
Invalid attribute.
264 ERROR_INVALID_STARTING_RING
Invalid starting ring.
265 ERROR_INVALID_DLL_INIT_RING
Invalid DLL INIT ring.
266 ERROR_CANNOT_COPY
Cannot copy.
267 ERROR_DIRECTORY
Used by DOSCOPY in doscall1.
268 ERROR_OPLOCKED_FILE
Oplocked file.
269 ERROR_OPLOCK_THREAD_EXISTS
Oplock thread exists.
270 ERROR_VOLUME_CHANGED
Volume changed.
271-273 Reserved.
274 ERROR_ALREADY_SHUTDOWN
System is already shut down.
275 ERROR_EAS_DIDNT_FIT
Buffer is not big enough to hold the EAs.
276 ERROR_EA_FILE_CORRUPT
EA file has been damaged.
277 ERROR_EA_TABLE_FULL
EA table is full.
278 ERROR_INVALID_EA_HANDLE
EA handle is invalid.
279 ERROR_NO_CLUSTER
No cluster.
280 ERROR_CREATE_EA_FILE
Cannot create the EA file.
281 ERROR_CANNOT_OPEN_EA_FILE
Cannot open the EA file.
282 ERROR_EAS_NOT_SUPPORTED
Destination file system does not support EAs.
283 ERROR_NEED_EAS_FOUND
Destination file system does not support EAs, and the source file's
EAs contain a need EA.
284 ERROR_DUPLICATE_HANDLE
The handle already exists.
285 ERROR_DUPLICATE_NAME
The name already exists.
286 ERROR_EMPTY_MUXWAIT
The list of semaphores in a muxwait semaphore is empty.
287 ERROR_MUTEX_OWNED
The calling thread owns one or more of the mutex semaphores in the
list.
288 ERROR_NOT_OWNER
Caller does not own the semaphore.
289 ERROR_PARAM_TOO_SMALL
Parameter is not large enough to contain all of the semaphore
records in the muxwait semaphore.
290 ERROR_TOO_MANY_HANDLES
Limit reached for number of handles.
291 ERROR_TOO_MANY_OPENS
There are too many files or semaphores open.
292 ERROR_WRONG_TYPE
Attempted to create wrong type of semaphore.
293 ERROR_UNUSED_CODE
Code is not used.
294 ERROR_THREAD_NOT_TERMINATED
Thread has not terminated.
295 ERROR_INIT_ROUTINE_FAILED
Initialization routine failed.
296 ERROR_MODULE_IN_USE
Module is in use.
297 ERROR_NOT_ENOUGH_WATCHPOINTS
There are not enough watchpoints.
298 ERROR_TOO_MANY_POSTS
Post count limit was reached for an event semaphore.
299 ERROR_ALREADY_POSTED
Event semaphore is already posted.
300 ERROR_ALREADY_RESET
Event semaphore is already reset.
301 ERROR_SEM_BUSY
Semaphore is busy.
302 Reserved
303 ERROR_INVALID_PROCID
Invalid process identity.
304 ERROR_INVALID_PDELTA
Invalid priority delta.
305 ERROR_NOT_DESCENDANT
Not descendant.
306 ERROR_NOT_SESSION_MANAGER
Requestor not session manager.
307 ERROR_INVALID_PCLASS
Invalid P class.
308 ERROR_INVALID_SCOPE
Invalid scope.
309 ERROR_INVALID_THREADID
Invalid thread identity.
310 ERROR_DOSSUB_SHRINK
Cannot shrink segment - DosSubSetMem.
311 ERROR_DOSSUB_NOMEM
No memory to satisfy request - DosSubAllocMem.
312 ERROR_DOSSUB_OVERLAP
Overlap of the specified block with a block of allocated memory -
DosSubFreeMem.
313 ERROR_DOSSUB_BADSIZE
Invalid size parameter - DosSubAllocMem or DosSubFreeMem.
314 ERROR_DOSSUB_BADFLAG
Invalid flag parameter - DosSubSetMem.
315 ERROR_DOSSUB_BADSELECTOR
Invalid segment selector.
316 ERROR_MR_MSG_TOO_LONG
Message too long for buffer.
317 ERROR_MR_MID_NOT_FOUND
Message identity number not found.
318 ERROR_MR_UN_ACC_MSGF
Unable to access message file.
319 ERROR_MR_INV_MSGF_FORMAT
Invalid message file format.
320 ERROR_MR_INV_IVCOUNT
Invalid insertion variable count.
321 ERROR_MR_UN_PERFORM
Unable to perform function.
322 ERROR_TS_WAKEUP
Unable to wake up.
323 ERROR_TS_SEMHANDLE
Invalid system semaphore.
324 ERROR_TS_NOTIMER
No timers available.
326 ERROR_TS_HANDLE
Invalid timer handle.
327 ERROR_TS_DATETIME
Date or time invalid.
328 ERROR_SYS_INTERNAL
Internal system error.
329 ERROR_QUE_CURRENT_NAME
Current queue name does not exist.
330 ERROR_QUE_PROC_NOT_OWNED
Current process does not own queue.
331 ERROR_QUE_PROC_OWNED
Current process owns queue.
332 ERROR_QUE_DUPLICATE
Duplicate queue name.
333 ERROR_QUE_ELEMENT_NOT_EXIST
Queue element does not exist.
334 ERROR_QUE_NO_MEMORY
Inadequate queue memory.
335 ERROR_QUE_INVALID_NAME
Invalid queue name.
336 ERROR_QUE_INVALID_PRIORITY
Invalid queue priority parameter.
337 ERROR_QUE_INVALID_HANDLE
Invalid queue handle.
338 ERROR_QUE_LINK_NOT_FOUND
Queue link not found.
339 ERROR_QUE_MEMORY_ERROR
Queue memory error.
340 ERROR_QUE_PREV_AT_END
Previous queue element was at end of queue.
341 ERROR_QUE_PROC_NO_ACCESS
Process does not have access to queues.
342 ERROR_QUE_EMPTY
Queue is empty.
343 ERROR_QUE_NAME_NOT_EXIST
Queue name does not exist.
344 ERROR_QUE_NOT_INITIALIZED
Queues not initialized.
345 ERROR_QUE_UNABLE_TO_ACCESS
Unable to access queues.
346 ERROR_QUE_UNABLE_TO_ADD
Unable to add new queue.
347 ERROR_QUE_UNABLE_TO_INIT
Unable to initialize queues.
349 ERROR_VIO_INVALID_MASK
Invalid function replaced.
350 ERROR_VIO_PTR
Invalid pointer to parameter.
351 ERROR_VIO_APTR
Invalid pointer to attribute.
352 ERROR_VIO_RPTR
Invalid pointer to row.
353 ERROR_VIO_CPTR
Invalid pointer to column.
354 ERROR_VIO_LPTR
Invalid pointer to length.
355 ERROR_VIO_MODE
Unsupported screen mode.
356 ERROR_VIO_WIDTH
Invalid cursor width value.
357 ERROR_VIO_ATTR
Invalid cursor attribute value.
358 ERROR_VIO_ROW
Invalid row value.
359 ERROR_VIO_COL
Invalid column value.
360 ERROR_VIO_TOPROW
Invalid TopRow value.
361 ERROR_VIO_BOTROW
Invalid BotRow value.
362 ERROR_VIO_RIGHTCOL
Invalid right column value.
363 ERROR_VIO_LEFTCOL
Invalid left column value.
364 ERROR_SCS_CALL
Call issued by other than session manager.
365 ERROR_SCS_VALUE
Value is not for save or restore.
366 ERROR_VIO_WAIT_FLAG
Invalid wait flag setting.
367 ERROR_VIO_UNLOCK
Screen not previously locked.
368 ERROR_SGS_NOT_SESSION_MGR
Caller not session manager.
369 ERROR_SMG_INVALID_SGID
Invalid session identity.
369 ERROR_SMG_INVALID_SESSION_ID
Invalid session ID.
370 ERROR_SMG_NOSG
No sessions available.
370 ERROR_SMG_NO_SESSIONS
No sessions available.
371 ERROR_SMG_GRP_NOT_FOUND
Session not found.
371 ERROR_SMG_SESSION_NOT_FOUND
Session not found.
372 ERROR_SMG_SET_TITLE
Title sent by shell or parent cannot be changed.
373 ERROR_KBD_PARAMETER
Invalid parameter to keyboard.
374 ERROR_KBD_NO_DEVICE
No device.
375 ERROR_KBD_INVALID_IOWAIT
Invalid I/O wait specified.
376 ERROR_KBD_INVALID_LENGTH
Invalid length for keyboard.
377 ERROR_KBD_INVALID_ECHO_MASK
Invalid echo mode mask.
378 ERROR_KBD_INVALID_INPUT_MASK
Invalid input mode mask.
379 ERROR_MON_INVALID_PARMS
Invalid parameters to DosMon.
380 ERROR_MON_INVALID_DEVNAME
Invalid device name string.
381 ERROR_MON_INVALID_HANDLE
Invalid device handle.
382 ERROR_MON_BUFFER_TOO_SMALL
Buffer too small.
383 ERROR_MON_BUFFER_EMPTY
Buffer is empty.
384 ERROR_MON_DATA_TOO_LARGE
Data record is too large.
385 ERROR_MOUSE_NO_DEVICE
Mouse device closed (invalid device handle).
386 ERROR_MOUSE_INV_HANDLE
Mouse device closed (invalid device handle).
387 ERROR_MOUSE_INV_PARMS
Parameters invalid for display mode.
388 ERROR_MOUSE_CANT_RESET
Function assigned and cannot be reset.
389 ERROR_MOUSE_DISPLAY_PARMS
Parameters invalid for display mode.
390 ERROR_MOUSE_INV_MODULE
Module not valid.
391 ERROR_MOUSE_INV_ENTRY_PT
Entry point not valid.
392 ERROR_MOUSE_INV_MASK
Function mask invalid.
393 NO_ERROR_MOUSE_NO_DATA
No valid data.
394 NO_ERROR_MOUSE_PTR_DRAWN
Pointer drawn.
395 ERROR_INVALID_FREQUENCY
Invalid frequency for beep.
396 ERROR_NLS_NO_COUNTRY_FILE
Cannot find COUNTRY.SYS file.
397 ERROR_NLS_OPEN_FAILED
Cannot open COUNTRY.SYS file.
398 ERROR_NLS_NO_CTRY_CODE
Country code not found.
398 ERROR_NO_COUNTRY_OR_CODEPAGE
Country code not found.
399 ERROR_NLS_TABLE_TRUNCATED
Table returned information truncated, buffer is too small.
400 ERROR_NLS_BAD_TYPE
Selected type does not exist.
401 ERROR_NLS_TYPE_NOT_FOUND
Selected type is not in file.
402 ERROR_VIO_SMG_ONLY
Valid from session manager only.
403 ERROR_VIO_INVALID_ASCIIZ
Invalid ASCIIZ length.
404 ERROR_VIO_DEREGISTER
VioDeRegister not allowed.
405 ERROR_VIO_NO_POPUP
Pop-up window not allocated.
406 ERROR_VIO_EXISTING_POPUP
Pop-up window on screen (NoWait).
407 ERROR_KBD_SMG_ONLY
Valid from session manager only.
408 ERROR_KBD_INVALID_ASCIIZ
Invalid ASCIIZ length.
409 ERROR_KBD_INVALID_MASK
Invalid replacement mask.
410 ERROR_KBD_REGISTER
KbdRegister not allowed.
411 ERROR_KBD_DEREGISTER
KbdDeRegister not allowed.
412 ERROR_MOUSE_SMG_ONLY
Valid from session manager only.
413 ERROR_MOUSE_INVALID_ASCIIZ
Invalid ASCIIZ length.
414 ERROR_MOUSE_INVALID_MASK
Invalid replacement mask.
415 ERROR_MOUSE_REGISTER
Mouse register not allowed.
416 ERROR_MOUSE_DEREGISTER
Mouse deregister not allowed.
417 ERROR_SMG_BAD_ACTION
Invalid action specified.
418 ERROR_SMG_INVALID_CALL
INIT called more than once, or invalid session identity.
419 ERROR_SCS_SG_NOTFOUND
New session number.
420 ERROR_SCS_NOT_SHELL
Caller is not shell.
421 ERROR_VIO_INVALID_PARMS
Invalid parameters passed.
422 ERROR_VIO_FUNCTION_OWNED
Save/restore already owned.
423 ERROR_VIO_RETURN
Non-destruct return (undo).
424 ERROR_SCS_INVALID_FUNCTION
Caller invalid function.
425 ERROR_SCS_NOT_SESSION_MGR
Caller not session manager.
426 ERROR_VIO_REGISTER
Vio register not allowed.
427 ERROR_VIO_NO_MODE_THREAD
No mode restore thread in SG.
428 ERROR_VIO_NO_SAVE_RESTORE_THD
No save/restore thread in SG.
429 ERROR_VIO_IN_BG
Function invalid in background.
430 ERROR_VIO_ILLEGAL_DURING_POPUP
Function not allowed during pop-up window.
431 ERROR_SMG_NOT_BASESHELL
Caller is not the base shell.
432 ERROR_SMG_BAD_STATUSREQ
Invalid status requested.
433 ERROR_QUE_INVALID_WAIT
NoWait parameter out of bounds.
434 ERROR_VIO_LOCK
Error returned from Scroll Lock.
435 ERROR_MOUSE_INVALID_IOWAIT
Invalid parameters for IOWait.
436 ERROR_VIO_INVALID_HANDLE
Invalid VIO handle.
437 ERROR_VIO_ILLEGAL_DURING_LOCK
Function not allowed during screen lock.
438 ERROR_VIO_INVALID_LENGTH
Invalid VIO length.
439 ERROR_KBD_INVALID_HANDLE
Invalid KBD handle.
440 ERROR_KBD_NO_MORE_HANDLE
Ran out of handles.
441 ERROR_KBD_CANNOT_CREATE_KCB
Unable to create kcb.
442 ERROR_KBD_CODEPAGE_LOAD_INCOMPL
Unsuccessful code-page load.
443 ERROR_KBD_INVALID_CODEPAGE_ID
Invalid code-page identity.
444 ERROR_KBD_NO_CODEPAGE_SUPPORT
No code page support.
445 ERROR_KBD_FOCUS_REQUIRED
Keyboard focus required.
446 ERROR_KBD_FOCUS_ALREADY_ACTIVE
Calling thread has an outstanding focus.
447 ERROR_KBD_KEYBOARD_BUSY
Keyboard is busy.
448 ERROR_KBD_INVALID_CODEPAGE
Invalid code page.
449 ERROR_KBD_UNABLE_TO_FOCUS
Focus attempt failed.
450 ERROR_SMG_SESSION_NON_SELECT
Session is not selectable.
451 ERROR_SMG_SESSION_NOT_FOREGRND
Parent/child session is not foreground.
452 ERROR_SMG_SESSION_NOT_PARENT
Not parent of requested child.
453 ERROR_SMG_INVALID_START_MODE
Invalid session start mode.
454 ERROR_SMG_INVALID_RELATED_OPT
Invalid session start related option.
455 ERROR_SMG_INVALID_BOND_OPTION
Invalid session bond option.
456 ERROR_SMG_INVALID_SELECT_OPT
Invalid session select option.
457 ERROR_SMG_START_IN_BACKGROUND
Session started in background.
458 ERROR_SMG_INVALID_STOP_OPTION
Invalid session stop option.
459 ERROR_SMG_BAD_RESERVE
Reserved parameters are not zero.
460 ERROR_SMG_PROCESS_NOT_PARENT
Session parent process already exists.
461 ERROR_SMG_INVALID_DATA_LENGTH
Invalid data length.
462 ERROR_SMG_NOT_BOUND
Parent is not bound.
463 ERROR_SMG_RETRY_SUB_ALLOC
Retry request block allocation.
464 ERROR_KBD_DETACHED
This call is not allowed for a detached PID.
465 ERROR_VIO_DETACHED
This call is not allowed for a detached PID.
466 ERROR_MOU_DETACHED
This call is not allowed for a detached PID.
467 ERROR_VIO_FONT
No font is available to support the mode.
468 ERROR_VIO_USER_FONT
User font is active.
469 ERROR_VIO_BAD_CP
Invalid code page specified.
470 ERROR_VIO_NO_CP
System displays do not support code page.
471 ERROR_VIO_NA_CP
Current display does not support code page.
472 ERROR_INVALID_CODE_PAGE
Invalid code page.
473 ERROR_CPLIST_TOO_SMALL
Code page list is too small.
474 ERROR_CP_NOT_MOVED
Code page was not moved.
475 ERROR_MODE_SWITCH_INIT
Mode switch initialization error.
476 ERROR_CODE_PAGE_NOT_FOUND
Code page was not found.
477 ERROR_UNEXPECTED_SLOT_RETURNED
Internal error.
478 ERROR_SMG_INVALID_TRACE_OPTION
Invalid start session trace indicator.
479 ERROR_VIO_INTERNAL_RESOURCE
VIO internal resource error.
480 ERROR_VIO_SHELL_INIT
VIO shell initialization error.
481 ERROR_SMG_NO_HARD_ERRORS
No session manager hard errors.
482 ERROR_CP_SWITCH_INCOMPLETE
DosSetProcessCp is unable to set a KBD or VIO code page.
483 ERROR_VIO_TRANSPARENT_POPUP
Error during VIO pop-up window.
484 ERROR_CRITSEC_OVERFLOW
Critical section overflow.
485 ERROR_CRITSEC_UNDERFLOW
Critical section underflow.
486 ERROR_VIO_BAD_RESERVE
Reserved parameter is not zero.
487 ERROR_INVALID_ADDRESS
Invalid physical address.
488 ERROR_ZERO_SELECTORS_REQUESTED
At least one selector must be requested.
489 ERROR_NOT_ENOUGH_SELECTORS_AVA
Not enough GDT selectors to satisfy request.
490 ERROR_INVALID_SELECTOR
Not a GDT selector.
491 ERROR_SMG_INVALID_PROGRAM_TYPE
Invalid program type.
492 ERROR_SMG_INVALID_PGM_CONTROL
Invalid program control.
493 ERROR_SMG_INVALID_INHERIT_OPT
Invalid inherit option.
494 ERROR_VIO_EXTENDED_SG
495 ERROR_VIO_NOT_PRES_MGR_SG
496 ERROR_VIO_SHIELD_OWNED
497 ERROR_VIO_NO_MORE_HANDLES
498 ERROR_VIO_SEE_ERROR_LOG
499 ERROR_VIO_ASSOCIATED_DC
500 ERROR_KBD_NO_CONSOLE
501 ERROR_MOUSE_NO_CONSOLE
502 ERROR_MOUSE_INVALID_HANDLE
503 ERROR_SMG_INVALID_DEBUG_PARMS
504 ERROR_KBD_EXTENDED_SG
505 ERROR_MOU_EXTENDED_SG
506 ERROR_SMG_INVALID_ICON_FILE
507 ERROR_TRC_PID_NON_EXISTENT
508 ERROR_TRC_COUNT_ACTIVE
509 ERROR_TRC_SUSPENDED_BY_COUNT
510 ERROR_TRC_COUNT_INACTIVE
511 ERROR_TRC_COUNT_REACHED
512 ERROR_NO_MC_TRACE
513 ERROR_MC_TRACE
514 ERROR_TRC_COUNT_ZERO
515 ERROR_SMG_TOO_MANY_DDS
516 ERROR_SMG_INVALID_NOTIFICATION
517 ERROR_LF_INVALID_FUNCTION
518 ERROR_LF_NOT_AVAIL
519 ERROR_LF_SUSPENDED
520 ERROR_LF_BUF_TOO_SMALL
521 ERROR_LF_BUFFER_CORRUPTED
521 ERROR_LF_BUFFER_FULL
522 ERROR_LF_INVALID_DAEMON
522 ERROR_LF_INVALID_RECORD
523 ERROR_LF_INVALID_TEMPL
523 ERROR_LF_INVALID_SERVICE
524 ERROR_LF_GENERAL_FAILURE
525 ERROR_LF_INVALID_ID
526 ERROR_LF_INVALID_HANDLE
527 ERROR_LF_NO_ID_AVAIL
528 ERROR_LF_TEMPLATE_AREA_FULL
529 ERROR_LF_ID_IN_USE
530 ERROR_MOU_NOT_INITIALIZED
531 ERROR_MOUINITREAL_DONE
532 ERROR_DOSSUB_CORRUPTED
533 ERROR_MOUSE_CALLER_NOT_SUBSYS
534 ERROR_ARITHMETIC_OVERFLOW
535 ERROR_TMR_NO_DEVICE
536 ERROR_TMR_INVALID_TIME
537 ERROR_PVW_INVALID_ENTITY
538 ERROR_PVW_INVALID_ENTITY_TYPE
539 ERROR_PVW_INVALID_SPEC
540 ERROR_PVW_INVALID_RANGE_TYPE
541 ERROR_PVW_INVALID_COUNTER_BLK
542 ERROR_PVW_INVALID_TEXT_BLK
543 ERROR_PRF_NOT_INITIALIZED
544 ERROR_PRF_ALREADY_INITIALIZED
545 ERROR_PRF_NOT_STARTED
546 ERROR_PRF_ALREADY_STARTED
547 ERROR_PRF_TIMER_OUT_OF_RANGE
548 ERROR_PRF_TIMER_RESET
639 ERROR_VDD_LOCK_USEAGE_DENIED
640 ERROR_TIMEOUT
641 ERROR_VDM_DOWN
642 ERROR_VDM_LIMIT
643 ERROR_VDD_NOT_FOUND
644 ERROR_INVALID_CALLER
645 ERROR_PID_MISMATCH
646 ERROR_INVALID_VDD_HANDLE
647 ERROR_VLPT_NO_SPOOLER
648 ERROR_VCOM_DEVICE_BUSY
649 ERROR_VLPT_DEVICE_BUSY
650 ERROR_NESTING_TOO_DEEP
651 ERROR_VDD_MISSING
691 ERROR_IMP_INVALID_PARM
692 ERROR_IMP_INVALID_LENGTH
693 MSG_HPFS_DISK_ERROR_WARN
730 ERROR_MON_BAD_BUFFER
731 ERROR_MODULE_CORRUPTED
2055 ERROR_LF_TIMEOUT
2057 ERROR_LF_SUSPEND_SUCCESS
2058 ERROR_LF_RESUME_SUCCESS
2059 ERROR_LF_REDIRECT_SUCCESS
2060 ERROR_LF_REDIRECT_FAILURE
32768 ERROR_SWAPPER_NOT_ACTIVE
32769 ERROR_INVALID_SWAPID
32770 ERROR_IOERR_SWAP_FILE
32771 ERROR_SWAP_TABLE_FULL
32772 ERROR_SWAP_FILE_FULL
32773 ERROR_CANT_INIT_SWAPPER
32774 ERROR_SWAPPER_ALREADY_INIT
32775 ERROR_PMM_INSUFFICIENT_MEMORY
32776 ERROR_PMM_INVALID_FLAGS
32777 ERROR_PMM_INVALID_ADDRESS
32778 ERROR_PMM_LOCK_FAILED
32779 ERROR_PMM_UNLOCK_FAILED
32780 ERROR_PMM_MOVE_INCOMPLETE
32781 ERROR_UCOM_DRIVE_RENAMED
32782 ERROR_UCOM_FILENAME_TRUNCATED
32783 ERROR_UCOM_BUFFER_LENGTH
32784 ERROR_MON_CHAIN_HANDLE
32785 ERROR_MON_NOT_REGISTERED
32786 ERROR_SMG_ALREADY_TOP
32787 ERROR_PMM_ARENA_MODIFIED
32788 ERROR_SMG_PRINTER_OPEN
32789 ERROR_PMM_SET_FLAGS_FAILED
32790 ERROR_INVALID_DOS_DD
32791 ERROR_BLOCKED
32792 ERROR_NOBLOCK
32793 ERROR_INSTANCE_SHARED
32794 ERROR_NO_OBJECT
32795 ERROR_PARTIAL_ATTACH
32796 ERROR_INCACHE
32797 ERROR_SWAP_IO_PROBLEMS
32798 ERROR_CROSSES_OBJECT_BOUNDARY
32799 ERROR_LONGLOCK
32800 ERROR_SHORTLOCK
32801 ERROR_UVIRTLOCK
32802 ERROR_ALIASLOCK
32803 ERROR_ALIAS
32804 ERROR_NO_MORE_HANDLES
32805 ERROR_SCAN_TERMINATED
32806 ERROR_TERMINATOR_NOT_FOUND
32807 ERROR_NOT_DIRECT_CHILD
32808 ERROR_DELAY_FREE
32809 ERROR_GUARDPAGE
32900 ERROR_SWAPERROR
32901 ERROR_LDRERROR
32902 ERROR_NOMEMORY
32903 ERROR_NOACCESS
32904 ERROR_NO_DLL_TERM
65026 ERROR_CPSIO_CODE_PAGE_INVALID
65027 ERROR_CPSIO_NO_SPOOLER
65028 ERROR_CPSIO_FONT_ID_INVALID
65033 ERROR_CPSIO_INTERNAL_ERROR
65034 ERROR_CPSIO_INVALID_PTR_NAME
65037 ERROR_CPSIO_NOT_ACTIVE
65039 ERROR_CPSIO_PID_FULL
65040 ERROR_CPSIO_PID_NOT_FOUND
65043 ERROR_CPSIO_READ_CTL_SEQ
65045 ERROR_CPSIO_READ_FNT_DEF
65047 ERROR_CPSIO_WRITE_ERROR
65048 ERROR_CPSIO_WRITE_FULL_ERROR
65049 ERROR_CPSIO_WRITE_HANDLE_BAD
65074 ERROR_CPSIO_SWIT_LOAD
65077 ERROR_CPSIO_INV_COMMAND
65078 ERROR_CPSIO_NO_FONT_SWIT
65079 ERROR_ENTRY_IS_CALLGATE
ΓòÉΓòÉΓòÉ 13. Specifications ΓòÉΓòÉΓòÉ
Specifications covered:
Γûá API Calls Made
Γûá Memory Usage
Γûá IX3 File Format
Γûá DBF File Format
Γûá DBT File Format
Γûá Custom Sort-Compare Function
Γûá Custom Build-Key
Γûá Custom Expression Parser
ΓòÉΓòÉΓòÉ 13.1. OS/2 API Calls Made ΓòÉΓòÉΓòÉ
The following are the API calls made by Bullet.
DosAllocMem DosClose DosCopy
DosCloseMutexSem DosCreateDir DosCreateMutexSem
DosDelete (*) DosErrClass DosExitList
DosForceDelete DosFreeMem DosGetDateTime
DosMapCase DosOpen DosQueryCollate
DosQueryCp DosQueryCtryInfo DosQueryCurrentDisk
DosQueryFSAttach DosQueryHType DosQuerySysInfo
DosMove DosRead DosReleaseMutexSem
DosRequestMutexSem DosResetBuffer DosScanEnv
DosSetFileLocks DosSetFilePtr DosSetFileSize
DosSetMaxFH DosSetRelMaxFH DosWrite
(*) DosForceDelete is used in favor of DosDelete.
ΓòÉΓòÉΓòÉ 13.2. Bullet Memory Usage ΓòÉΓòÉΓòÉ
Memory is committed when allocated, using the PAG_COMMIT and the PAG_WRITE
flags. This is memory allocated by Bullet itself. Additional memory needs are
made by your code, such as parameter pack data, key buffers, and data record
buffers.
Code
Bullet uses 8 pages for code, or less than 32KB.
Data
Γûá Shared Data
A single page of shared memory is used by all Bullet processes.
Γûá Instance Data
One page of private memory is used by each Bullet processes.
Γûá Handle Data
One page of private memory is used by each open Bullet index file. For open
data files, one page of private memory is used for files with 121 or fewer
fields. Two pages are used for files with 249 or fewer fields. Thereafter, one
page for each additional 128 fields, up to 1024 max fields.
For example, if one machine is running 2 Bullet processes, each with 10 open
data files with 12 fields each, and 10 index files (one for each data file),
its total memory usage is:
Total code is 32KB.
Shared data is 4KB.
Instance data is 2 processes * 4KB, or 8KB (plus INIT_XB use shown
below).
DBF file data is 2 * 10 files * 4KB, or 80KB.
Index file data is 2 * 10 files * 4KB, or 80KB.
Total memory committed by Bullet for the above is 200KB, plus code and data of
your two applications (or your single application, if the same application is
being run twice). With no files open, for example when starting your program,
only 40KB is committed. Thereafter, 4KB per open file. The memory is freed
when the file is closed.
Γûá Temporary Data
Additional memory is allocated on a temporary basis, where the allocation is
requested on entry to, and is freed upon exiting from, the routine called.
INIT_XB's allocation can be considered permanent since its allocations are not
released until the program has ended or EXIT_XB is issued. The following are
the routines and the single requested amount:
Routine Memory Allocated, in KB
INIT_XB 8 for 1024 MAX_FILES version; 4KB for 100 and 250 MAX_FILES versions
BACKUP_FILE_XB 8
CREATE_DATA_XB 4 for 1-121 fields, 8 for 122-249 fields, ...
CREATE_INDEX_XB 4
PACK_RECORDS_XB adjustable, 128 default (less if file is smaller)
REINDEX_XB adjustable, 144 default (minimum size is 48KB)
UPDATE_XB varies: 40 + sum of record lengths where AP[].recNo!=0
Γûá Stack Data
Stack requirements are 8KB minimum for Bullet. No single stack allocation
requests more than 4KB at a time (ie all changes to ESP (the CPU stack
pointer) are less than 4KB at any one time), but some routines nest and
require up to the minimum 8KB in total. The minimum recommended stack size
for your Bullet application is 16KB. It's likely that you need to use a much
larger size for your main program's stack use. If you have any doubt about
stack space, double it, twice even.
ΓòÉΓòÉΓòÉ 13.3. IX3 File Format ΓòÉΓòÉΓòÉ
The IX3 index file is composed of a header followed by node data. The header
layout is detailed below, followed by the node format.
Index Header
// nnn, where nnn is the offset of that item relative to the start of the file
CHAR fileID[4]; // 000 file id = '31ch'
ULONG nodeSize; // 004 size of a node, in bytes
ULONG rootNode; // 008 root node (1-based)
ULONG noKeys; // 012 total number of keys
ULONG availNode; // 016 next available node (link to, 0 if none, 1-based)
ULONG freeNode; // 020 next free node
ULONG keyLength; // 024 length of key
ULONG maxKeys; // 028 maximum number of keys on a node
ULONG codePage; // 032 code page from CreateIndexFile
ULONG countryCode; // 036 country code from CreateIndexFile
ULONG sortFunction; // 040 system (1-9) or custom (10-19)
// high word has flags: bit0=1 dups allowed
// bit1-15 rez
// Translated key expression as done by Parser during CreateIndex and Reindex.
// More details on this is in the Custom Expression Parser Specifications.
// For each key part in KH.expression a 4-byte structure is used in XLATEX:
typedef struct _XLATEX {
CHAR ftype; // field type (C,N,L, etc.),if bit7=1 and C then do UPPER key
CHAR length; // bytes to use starting at offset (never > 64)
SHORT offset; // byte offset into data record that length bytes are to be used
} XLATEX;
ULONG xlateCount; // 044 number of key fields (64/4=16 max fields)
XLATEX xlateExpression[16]; // 048 key construct info (16 dword's worth)
CHAR miscWorkspace[236]; // 112-347 B-tree workspace
CHAR expression[160]; // 348 key expression, user (0-Terminated)
ULONG CTsize; // 508 size of collate table following
CHAR collateTable[256]; // 512 collate table, fill at CreateIndexXB
CHAR rez[256]; // 768 to 1023 reserved (header size=1024 bytes)
Node Data
Directly after the header the node data starts. Each node is either 512, 1024,
or 2048 bytes long. Each node contains a key count, indicating the number of
active keys on the node, followed by key data.
// nnn, where nnn is the offset of that item relative to the start of the node
CHAR keyCount; // 000 1 to maxKeys (in header above)
ULONG backNode; // 001 previous node page, or 0 if this node is a leaf
XNODE node[maxKeys]; // 005...
For each key on the node:
typedef struct _XNODE {
CHAR keyValue[keyLength]; // 005 actual key (keyLength from header)
ULONG recordNo; // 005+keyLength record number for key
ULONG fwdNode; // 005+keyLength+4 next node page, or 0 if leaf
} XNODE;
backNode and fwdNode are node numbers. The first node is 1, and is located
directly after the header. The last node used is at header:freeNode-1. Each
fwdNode of a key is also the next key's backNode. If the node has had all keys
removed, its node number is placed on the top of the header:availNode list, and
the first 4 bytes of the node are used as a link to the previous list top.
ΓòÉΓòÉΓòÉ 13.4. DBF File Format ΓòÉΓòÉΓòÉ
The DBF data file is composed of a header, field descriptors, one per field,
and the actual record data. The header layout is detailed below, followed by
the field descriptor layout and then the description of the data record. The
standard DBF file has a record length limit of 4000 bytes. Creating record
lengths greater than 4000 is allowed in Bullet, but these files may not be
recognized by other applications if you do so.
DBF Header
// nnn, where nnn is the offset of that item relative to the start of the file
CHAR fileID; // 000 file id byte
CHAR lastUpdateYR; // 001 binary year-1900
CHAR lastUpdateMO; // 002 binary month (1-12)
CHAR lastUpdateDA; // 003 binary day (1-31)
ULONG noRecords; // 004 total number of records
SHORT headerLength; // 008 length of data header
SHORT recordLength; // 010 record length
SHORT nada; // 012 reserved
CHAR xactionFlag; // 014 flag indicating incomplete dBASE transaction (n/a)
CHAR encryptFlag; // 015 flag indicating encrypted (n/a)
CHAR filler[16]; // 016 fill to 32 bytes
Field Descriptors
For each field, a descriptor is stored in the DBF. The first descriptor starts
directly after the header, at file offset 32 (the 33rd byte). Each descriptor
is 32 bytes. After the last descriptor, a byte with ASCII value 13 (0x0D) is
stored. Following this byte, the record data starts.
// nnn, where nnn is offset of item relative to the start of the descriptor
CHAR fieldName[11]; // 000 ASCII, UPPER, underscore, zero-filled, (0T)
CHAR fieldType; // 011 UPPER C,N,D,L,M
ULONG fieldDA; // 012 not used
CHAR fieldLength; // 016 1-255 bytes, depending on fieldType
CHAR fieldDC; // 017 places right of decimal point
SHORT altFieldLength; // 018 alternate field length when fieldLength==0
CHAR filler[12]; // 020 not used
// altFieldLength is proprietary to Bullet, and can be used if Xbase
// compatibility is not required and fields need to be larger than 255 bytes.
// To use it, set fieldLength=0 and altFieldLength to > 255 bytes.
Record Data
The DBF data are free-form, fixed-length records. Each data record starts with
a one-byte 'tag' field, which is implicitly defined for all records (hence, it
is not a formal field and has no descriptor). Following the tag field is the
first field of the record, and following that field (whose length is described
in the field's descriptor) is the next field, and so on. No separators are
used between fields. After the very last data record in the file, DBF
specification dictates that an end of file marker be placed, so at the end of
the file is a byte of value ASCII 26 (0x1A).
Record layout is as you define in your application. It must match the layout
as described in the field descriptors, byte-for-byte.
Increasing DBF Performance
Records are stored in the order they were written. To improve performance,
especially indexed-sequential access, the data file may be sorted, or
clustered, by reading each record in primary key order, then writing that
record to a new DBF data file. Repeat for each record. After all records have
been written, reindex the newly created DBF data file (and all related index
files). After this, delete the old files (data and index), and rename the new
ones to the filenames required. This technique maximizes cache efficiency, and
can easily offer 10x performance increase in access speed.
ΓòÉΓòÉΓòÉ 13.5. DBT File Format ΓòÉΓòÉΓòÉ
The DBT memo file is composed of a header followed by memo data, stored in one
or more blocks. The header layout is detailed below, followed by the memo
record.
DBT Header
// nnn, where nnn is the offset of that item relative to the start of the file
ULONG memoAvailBlock; // 000 next available block (header is block 0)
ULONG memoRez; // 004 not used
CHAR memoFilename[8]; // 008 filename proper (first 8 of filename proper)
ULONG memoRez2; // 016 not used (apparently)
ULONG memoBlockSize; // 020 block size, must be at least 24
// the rest of the header block (to block size bytes) is unused
Memo Record
// nnn, where nnn is offset of item relative to the start of the memo record
ULONG memoAvail; // 000 next available link
ULONG memoSize; // 004 size of data (including this and memoAvail)
CHAR memoData; // 008 for as many bytes as memoSize, less 8
A memo may use one or more blocks (each block is a fixed size), but allocations
are always contiguous. Unused bytes after the memo data (to the end of the
last block allocated to that memo record) are undefined. memoAvail is 0x8FFFF
for all active memo records. For deleted memo records, memoAvail is used as a
link in the memoAvail list. memoSize is the total bytes used by the memo,
including the memoAvail and memoSize data, so it is the size of the real data +
8 bytes.
Removing Memo Records
A similar technique to that above could be used to remove memo records from a
DBT. Instead of using PACK_RECORDS_XB, read each DBF record in sequence, and
if not to be deleted, write that record to a new DBF data file and its memo
records to a new DBT memo file. If the record read is to be deleted, skip it
(and its memo record(s)) and continue with the next DBF data record. Repeat
while more records. Delete or archive the old DBF/DBT pair, and use the new
files in their place. Reindex. Also see compact.c on the distribution disk
for an example technique of compacting a database.
ΓòÉΓòÉΓòÉ 13.6. Custom Sort-Compare Function ΓòÉΓòÉΓòÉ
Bullet provides 10 custom sort-compare functions, in addition to the 6
intrinsic sort-compare functions (ASCII, NLS, and the four integer compares).
The custom function you supply is not actually a sort function, as the name
implies, but a compare function. Basically, two strings are supplied and your
function determines string1's relation to string2 (<, >, or ==).
The strings supplied (via pointers) are not C strings, and they are not
(necessarily) 0-terminated. A count value is passed, indicating the number of
bytes to compare. The handle of the index file for which this compare is being
done is also supplied, so that you can interrogate the index file state
(STAT_INDEX_XB) for any additional information required.
In addition to the compare function this routine performs, a special-case call
is made to this routine requesting a pointer to a string of HIGH-VALUES for
this sort compare. The pointer must be to a static memory area that exists for
as long as the index file is open, and must be at least as long as count. This
special-case call is indicated by both string pointers==NULL.
To use a custom sort-compare function, first use SET_SYSVARS_XB to assign the
custom sort ID (10 to 19) with the function's address pointer. Once assigned,
an index file may be created with its CIP.sortFunction set to the sort ID
(10-19). Also, any previously created index file with a custom sort ID may now
be opened (but only after you used SET_SYSVARS_XB to assign the sort-compare
function pointer). During the index file create, the sort ID you specified for
the create is stored in the index file. When that index file is later opened,
that same sort ID is used, and so requires that the custom sort-compare
function already be assigned (with SET_SYSVARS_XB) before opening the index
file. This means that you need to be consistent in your custom sort ID
numbering, since each index created forever uses that sort ID you specified.
It's simple to create a custom sort-compare function. The calling convention
is APIENTRY (DOSX32 = __cdecl, OS/2= _syscall, Win32=_stdcall), and the
parameters are passed to your function on the stack (by Bullet). A sample
prototype for a custom sort-compare function follows:
LONG APIENTRY YourCustomSort10(PVOID str1,
PVOID str2,
ULONG count,
ULONG handle);
If the pointers are not NULL, your routine is to compare str1 to str2, for
count bytes, and is to return:
-1 if str1 is less than str2
0 if str1 is equal to str2
1 if str1 is greater than str2
str is not a C string, but is of type void. Cast as required,
depending on the data expected.
If str1 and str2 are both NULL, your routine must return a pointer to a static
object that contains HIGH-VALUES for the object type. For example, if the
sort-compare is for IEEE floating-point, then the function is to return a
pointer to a static data area filled with the highest floating-point value.
Depending on your sort-compare routine's functionality, you may need just a
single high-value, or multiple high-values, one after the other (e.g., if you
are supporting compound key values for binary keys). The count parameter
indicates the total bytes needed, so divide by the object size to get the
number of objects required. Be aware that the object size (in count) is +2
bytes for the enumerator if DUPS_ALLOWED was specified when the index file was
created. This high-values object is used in the REINDEX_XB routine, and also
the LAST_KEY_XB and GET_LAST_XB routines.
ΓòÉΓòÉΓòÉ 13.7. Custom Build-Key ΓòÉΓòÉΓòÉ
Bullet provides an internal build-key routine that constructs the key from the
data record supplied. The internal routine can be overloaded by your custom
build-key routine if you need additional functionality. It may be used in
conjunction with a custom sort-compare function, or an intrinsic Bullet
sort-compare.
Developing a custom build-key routine requires delving into the internal Bullet
data structures. It is more complicated than a custom sort-compare function,
but not really any more complex. The handle of the index file is passed, and
using this, STAT_INDEX_XB is called to get the SIP.herePtr pointer. This is
the pointer to the internal Bullet data structure for this index file. What
needs to be accessed in this structure is the translated key expression. From
this, you have the starting offset in the data record, and the byte count to
use, for each key component (up to 16 components per key). The offset value as
stored in the XLATEX structure does not include the tag field byte. Therefore,
to locate to the correct offset, add 1 to the value in offset. For example,
XLATEX.offset=0 means to use the first field, which is the first byte after the
tag field byte, but the physical offset, as referenced to recPtr, is not at
offset=0, but is at offset=1.
This translated key expression structure is:
// (This is an excerpt from the IX3 header format)
// Translated key expression as done by Parser during CreateIndex and Reindex.
// More details on this is in the Custom Expression Parser Specifications.
// For each key part in KH.expression a 4-byte structure is used:
typedef struct _XLATEX {
CHAR ftype; // field type (C,N,L, etc.),if bit7=1 and C then do UPPER key
CHAR length; // bytes to use starting at offset (never > 64)
SHORT offset; // byte offset into data record that length bytes are to be used
} XLATEX; // (note that offset does not count tag field byte)
ULONG xlateCount; // 044 number of key fields (64/4=16 max fields)
XLATEX xlateExpression[16]; // 048 key construct info (16 dword's worth)
xlateExpression is at offset +48 relative the IX3 index header. However,
SIP.herePtr points to -384 relative the IX3 index header start. Therefore, to
locate to xlateExpression, you must add 384 to 48. This means that
xlateExpression[0].ftype is located at SIP.herePtr+432. The number of valid
key components in xlateExpression is stored in xlateCount (at SIP.herePtr+428).
The calling convention for your custom build-key function is APIENTRY (or
_System, or __syscall for some compilers), and the parameters are passed to
your function on the stack (by Bullet). A sample prototype for a build-key
function follows:
ULONG APIENTRY YourBuildKey(ULONG handle,
PVOID recordPtr,
PVOID keyPtr,
PULONG keyLenPtr
PULONG sortFuncPtr);
Using the data from xlateExpression, you are to build a key from the data
record located at the passed pointer, recordPtr, and are store the built key in
the buffer located at keyPtr. For each key component, you copy from the data
record xlateExpression[].length bytes starting at xlateExpression[].offset+1
(given the 1-byte tag field which is not accounted for otherwise), and build
other key components after previously build parts. If the index file allows
duplicate keys (DUPS_ALLOWED is flagged in SIP.sortFunction), then append an
enumerator to the end of the key proper. The handle of the index file is
passed, which is used when calling STAT_INDEX_XB (to get SIP.herePtr). The
return is 0 if successful, or an appropriate Bullet error code (EXB_) should be
used. In addition, the key length is placed in the ULONG data pointed to by
keyLenPtr (SIP.keyLength may be used), and the sort-compare function is placed
in the ULONG data pointed to by sortFuncPtr (SIP.sortFunction may be used).
The routine is also to check if the tag field of the data record matches the
skip-tag value, as set by SET_SYSVARS_XB. If the tag field matches,
WRN_SKIP_KEY is to be returned as the 'error' code. The key is built
regardless of a match.
ΓòÉΓòÉΓòÉ 13.8. Custom Expression Parser ΓòÉΓòÉΓòÉ
Bullet provides an internal key expression parser routine that constructs the
translated key expression stored in the index file header. The internal
routine can be overloaded by your custom expression parser routine if you need
additional functionality. It may be used in conjunction with a custom
sort-compare function, with a custom build-key routine, or with an intrinsic
Bullet sort-compare.
Developing a custom expression parser routine requires delving into the
internal Bullet data structures. It is more complicated than a custom
sort-compare function, and it is also much more complex. Unlike the custom
sort-compare and build-key functions, no handle is passed to the parser. This
is because, rather than using the handle to get the SIP.herePtr, this pointer
is passed directly to this routine. This is the pointer to the internal Bullet
data structure for this index file. What needs to be accessed in this
structure is the translated key expression location, as well as the text
version of the key expression, as supplied by the programmer/user. To the
XLATEX data you place the starting offset in the data record, and the byte
count to use, for each key component you parse from the key expression (up to
16 components per key). The offset value as stored in the XLATEX structure
does not include the tag field byte. Therefore, the correct offset to store is
the physical offset within the record, minus 1. For example, XLATEX.offset=0
should be used for the offset of the first field, which is the first byte after
the tag field byte. For each component parsed, an XLATEX data structure is
added to the xlateExpression data area (up to 16). Unused XLATEX components
must be set to 0. When all components have been stored, the xlateCount value
is set to the number of key components stored.
DHDptr is the data header pointer. It is -352 bytes relative the DBF data
header. However, rather than using absolute addressing to locate field
descriptor data (needed for parsing), it's recommended that the DBF handle be
obtained from the KHptr structure. Since no file handles are passed, you must
read the xbLink handle value from the index file header. The xbLink handle is
stored at KHptr+12. With this handle, you call the GET_DESCRIPTOR_XB routine
to obtain field descriptor info for each field.
This translated key expression structure, and text expression location, is at:
// (This is an excerpt from the IX3 header format)
// Translated key expression as done by Parser during CreateIndex and Reindex.
// For each key part in KH.expression a 4-byte structure is used:
typedef struct _XLATEX {
CHAR ftype; // field type (C,N,L, etc.),if bit7=1 and C then do UPPER key
CHAR length; // bytes to use starting at offset (never > 64)
SHORT offset; // byte offset into data record that length bytes are to be used
} XLATEX; // (note that offset does not count tag field byte)
ULONG xlateCount; // 044 number of key fields (64/4=16 max fields)
XLATEX xlateExpression[16]; // 048 key construct info (16 dword's worth)
: // 112-347 :
CHAR expression[160]; // 348 key expression, user (0-Terminated)
xlateExpression is at offset +48 relative the IX3 index header. However,
KHptr, passed to this routine, points to -384 relative the IX3 index header
start. Therefore, to locate to xlateExpression, you must add 384 to 48. This
means that xlateExpression[0].ftype is located at KHptr+432. The number of
valid key components in xlateExpression is stored in xlateCount (at KHptr+428).
To text key expression string, which you are to parse, is located at KHptr+732.
This is identical to the expression passed during CREATE_INDEX_XB (and it is
CREATE_INDEX_XB that calls this parser routine).
The calling convention for your custom key expression parser function is
APIENTRY (or _System, or __syscall for some compilers), and the parameters are
passed to your function on the stack (by Bullet). A sample prototype for a
build-key function follows:
ULONG APIENTRY YourKeyExpressionParser(PVOID DHDptr,
PVOID KHptr,
PULONG keyLenPtr);
You are to parse the text key expression at KHptr+732 and store the key
component XLATEX structure values to the XLATEX structure, one for each key
component parsed. In addition, the key length (the sum of the XLATEX.length
fields) is placed in the ULONG data pointed to by keyLenPtr. The keylength may
not exceed 64 bytes. If DUPS_ALLOWED is flagged, add two to the sum of the
XLATEX.length fields for the enumerator word.
Note: The key expression has been mapped to upper-case and 0-filled by the
time this routine is called.
This is probably the most difficult part of customizing Bullet. However, the
difficulty lies not with Bullet, but how you parse. The idea is simple -- you
are to generate a xlateCount value, and for each key component (ie
non-contiguous, non-same-type run in the data record), an XLATEX variable
describing the method to build that key component out of the data record (type,
length, and starting offset) is stored. The text key expression is available
in the index header, and the destination to write to is there, also. You do
need to read the index header at KHptr+12 (ULONG) to obtain the DBF handle for
this index file before you can parse the expression. This because you need to
know about the record field names, types, and lengths before you can parse the
key expression. The matter not covered here is that of parsing the expression,
which is left to the programmer. Any lexical parser algorithm may be used, or
you may even do no parsing at all, and simply hard-code values into the XLATEX
structures.
If you've gotten this far, you may find the following data structures useful.
The numbers at // nnn are offsets relative the SIP.herePtr and SDP.herePtr
pointers. For example, at SIP.herePtr+352 is a ULONG of the number of key
searches requested. These could be monitored in a separate thread.
Relative SIP.herePtr:
ULONG fType; // 000 bit0=0 for index file, btree
ULONG flags; // 004 bit0=1 is dirty
// bit1=1 full lock (count stored in KH.lockCount)
// bit2=1 shared lock (if bit1=1)
// bit3-14 reserved (=0)
// bit15=1 no coalesce on key delete
// 006 BYTE, progress of reindex (0,1-99)
// 007 BYTE, 0
PVOID morePtr; // 008 ptr to additional header info, if ever needed
ULONG xbLink; // 012 related XB data file handle
ULONG asMode; // 016 access-sharing-cache mode of open
CHAR filename[260]; // 020 filename at open (0T)
ULONG currKeyRecNo; // 280 current rec number assigned to KH.currKey
CHAR currKey[64]; // 284 current key value
ULONG rez0; // 348 allow for 0-terminated string
ULONG searches; // 352 keys searched for
ULONG seeks; // 356 nodes seeked
ULONG hits; // 360 seeks satisfied without disk access
ULONG keysDeleted; // 364 keys deleted since last zeroed
ULONG keysStored; // 368 keys added since last zeroed
ULONG nodesSplit; // 372 splits needed on insert since last zeroed
ULONG nodesMadeAvail; // 376 nodes made available from deleting keys
ULONG lockCount; // 380 active full-lock count
// the IX3 index header follows at 384+
Relative SDP.herePtr:
ULONG fType; // 000 bit0=1 for DBF data file, XB
ULONG flags; // 004 bit0=1 is dirty
// bit1=1 full lock
// bit2=1 shared lock (if bit1=1)
// bit3-15 reserved (=0)
// 006 BYTE, progress of pack (0,1-99)
// 007 BYTE, 0
PVOID morePtr; // 008 ptr to additional header info, if ever needed
ULONG noFields; // 012 number of fields in this data file
ULONG asMode; // 016 access-sharing-cache mode of open
CHAR filename[260]; // 020 filename at open (0T)
ULONG lockCount; // 280 only when dec'ed to 0 do full unlock
ULONG memoAvailBlock; // 284 next available block (header is block 0)
ULONG memoUnk1; // 288 not used
CHAR memoFilename[8]; // 292 filename proper (first 8 of filename proper)
ULONG memoUnk2; // 300 not used (apparently)
ULONG memoBlockSize; // 304 block size, must be at least 24 to cover header!
ULONG memoHandle; // 308 handle of open memo file
ULONG memoFlags; // 312 bit0=1 is dirty
ULONG memoLastNo; // 316 last accessed memo number (if not 0)
ULONG memoLastLink; // 320 link data for last accessed memo
ULONG memoLastSize; // 324 size of last accessed memo (in bytes, w/OH)
ULONG align32[6]; // 328 (align to even32)
// the DBF data header follows at +352
ΓòÉΓòÉΓòÉ 14. Bullet Is... ΓòÉΓòÉΓòÉ
Bullet is a thread-safe, multi-process capable database engine toolkit for
OS/2, DOSX32, and Win32. It provides pre-built and tested access methods to
data and index files for application programmers. It is not an end-user
Database Management System (DBMS), but it is a tool that could be used to
develop one. Bullet is compact, efficient, and very fast. It can be
configured to use custom key-build, sort-compare functions, and expression
parser routines to extend the built-in functionality, and even
programmer-supplied OS API calls to replace the internal ones.
The standard data format is DBF (dBASE 3+ and later). The supported memo
format is DBT (dBASE 4 and later). Index-only support can be enabled and with
this any data file format may be used (the data maintained by the programmer
then). Also, the DBF standard may be extended by using binary field values and
fields larger than 255 bytes. Index files are NLS-compatible and use an
efficient b-tree structure. Files may be any size supported by the OS, up to
4GB. Up to 1024 files may be opened and in use by any one process, with any
number of processes active.
The Bullet API consists of a wide assortment of routines, from low-level OS
calls to high-level transaction-list routines that can process hundreds of
files per transaction, with roll-back on error. Network support is included,
and makes use of operating system features such as atomic re-locking, and
shared locks that allow other processes read-access to region-locked files.
Bullet is simple to use, and may easily be modified by using function wrappers
around groups of related Bullet routines. If you don't like working with the
parameter packs, use a wrapper and call as you like. Bullet works the way you
are used to working.
This online manual is a complete introduction and programmer reference for
Bullet. Sample code is included, with more still on disk.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
A foreign key is a column value in one table used as a primary key in a second
table. For example, if one table has two fields: employee name (primary key)
and department code, and a second table has two fields: department code
(primary key) and department name, then department code in the first table is
considered a foreign key, since it may be used as the primary key value when
searching the second table. Joins and views make use of foreign keys.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Null pack
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _ACCESSPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to access */
LONG recNo; /* IO, record number */
PVOID recPtr; /* I, programmer's record buffer */
PVOID keyPtr; /* I, programmer's key buffer */
PVOID nextPtr; /* I, NULL if not xaction, else next AP in list */
} ACCESSPACK; /* AP */
typedef ACCESSPACK *PACCESSPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _CALLBACKPACK {
ULONG sizeIs; /* structure size (current 16 bytes) */
ULONG callMode; /* 0=from reindex; 1=from DBF pack */
ULONG handle; /* file handle */
ULONG data1; /* for callMode=0/1: progress percent (1-99,0) */
} CALLBACKPACK; /* CBP */
typedef CALLBACKPACK *PCALLBACKPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _COPYPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to copy */
PSZ filenamePtr; /* I, filename to use (drv:path must exist if used) */
} COPYPACK; /* CP */
typedef COPYPACK *PCOPYPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _CREATEDATAPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
ULONG noFields; /* I, 1 to 254 */
PFIELDDESCTYPE fieldListPtr; /* I, descriptor list, 1 per field */
ULONG fileID; /* I, 0x03 for std DBF, 0x8B for DBF+DBT */
} CREATEDATAPACK; /* CDP */
typedef CREATEDATAPACK *PCREATEDATAPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _CREATEINDEXPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
PSZ keyExpPtr; /* I, e.g., "SUBSTR(LNAME,1,4)+SSN" */
LONG xbLink; /* I, opened data file handle this indexes */
ULONG sortFunction; /* I, 1-9 system, 10-19 custom */
ULONG codePage; /* I, 0=use process default */
ULONG countryCode; /* I, 0=use process default */
PVOID collatePtr; /* I, NULL=use cc/cp else use passed table for sort */
ULONG nodeSize; /* I, 512, 1024, or 2048 */
} CREATEINDEXPACK; /* CIP */
typedef CREATEINDEXPACK *PCREATEINDEXPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _FIELDDESCTYPE {
BYTE fieldName[11]; /* IO, upper A-Z and _; 1-10 chars, 0-filled, 0-term */
BYTE fieldType; /* IO, C,D,L,N, or M */
LONG fieldDA; /* x, offset within record (run-time storage option) */
BYTE fieldLen; /* IO, C=1-255,D=8,L=1,N=1-19,M=10 */
BYTE fieldDC; /* IO, fieldType=N then 0-15 else 0 */
USHORT altFieldLength;/* IO, 0 */
BYTE filler[12]; /* I, 0 */
} FIELDDESCTYPE; /* nested in DESCRIPTORPACK */
typedef FIELDDESCTYPE *PFIELDDESCTYPE;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _DESCRIPTORPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of DBF file */
ULONG fieldNumber; /* IO, first field is 1 */
ULONG fieldOffset; /* O, offset of field within record (delete tag=offset 0) */
FIELDDESCTYPE FD; /* IO FD.fieldName, O rest of FD */
} DESCRIPTORPACK; /* DP */
typedef DESCRIPTORPACK *PDESCRIPTORPACK;
};
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _DOSFILEPACK {
ULONG func;
ULONG stat;
PSZ filenamePtr; /* I, filename to use */
ULONG handle; /* IO, handle of open file */
ULONG asMode; /* I, access-sharing-cache mode */
ULONG bytes; /* IO, bytes to read, write, length of */
LONG seekTo; /* IO, seek to offset, current offset */
ULONG method; /* I, seek method (0=start of file, 1=current, 2=end) */
PVOID bufferPtr; /* I, buffer to read into or write from */
ULONG attr; /* I, attribute to create file with */
PSZ newNamePtr; /* I, name to use on rename */
} DOSFILEPACK; /* DFP */
typedef DOSFILEPACK *PDOSFILEPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _EXITPACK {
ULONG func;
ULONG stat;
} EXITPACK; /* EP */
typedef EXITPACK *PEXITPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _HANDLEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file */
} HANDLEPACK; /* HP */
typedef HANDLEPACK *PHANDLEPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _INITPACK {
ULONG func;
ULONG stat;
ULONG JFTsize; /* I, max opened files (20-1024+) */
ULONG versionDOS; /* O, e.g., 230 for 2.30 */
ULONG versionBullet; /* O, e.g., 2019 for 2.019 */
ULONG versionOS; /* O, e.g., 4=OS/2 32-bit */
PVOID exitPtr; /* O, function pointer to EXIT_XB routine */
} INITPACK; /* IP */
typedef INITPACK *PINITPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _LOCKPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of Bullet file to lock */
ULONG xlMode; /* I, index lock mode (0=exclusive, 1=shared) */
ULONG dlMode; /* I, data lock mode (0=exclusive, 1=shared) */
LONG recStart; /* I, if data, first record # to lock, or 0 for all */
ULONG recCount; /* I, if data and recStart!=0, # records to lock */
PVOID nextPtr; /* I, NULL if not xaction, else next LP in list */
} LOCKPACK; /* LP */
typedef LOCKPACK *PLOCKPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _MEMODATAPACK {
ULONG func;
ULONG stat;
ULONG dbfHandle; /* I, handle of DBF file to which this memo file belongs */
ULONG memoBypass; /* I, memo bypass function to do, if any */
PVOID memoPtr; /* I, ptr to memo record buffer */
ULONG memoNo; /* IO, memo record number (aka block number) */
ULONG memoOffset; /* I, position within record to start read/update */
ULONG memoBytes; /* IO, number of bytes to read/update */
} MEMODATAPACK; /* MDP */
typedef MEMODATAPACK *PMEMODATAPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _MEMORYPACK {
ULONG func;
ULONG stat;
ULONG memory; /* O, not used in OS/2 */
} MEMORYPACK; /* MP */
typedef MEMORYPACK *PMEMORYPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _OPENPACK {
ULONG func;
ULONG stat;
ULONG handle; /* O, handle of file opened */
PSZ filenamePtr; /* I, Bullet file to open */
ULONG asMode; /* I, access-sharing-cache mode */
LONG xbLink; /* I, if index open, xbLink=handle of its opened DBF */
} OPENPACK; /* OP */
typedef OPENPACK *POPENPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _QUERYSETPACK {
ULONG func;
ULONG stat;
ULONG item; /* I, Bullet sysvar item to get/set */
ULONG itemValue; /* IO, current/new value */
} QUERYSETPACK; /* QSP */
typedef QUERYSETPACK *PQUERYSETPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _REMOTEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle of file, or if 0, use RP.drive */
ULONG drive; /* I, drive (1=A,2=B,3=C,...0=current) to check */
ULONG isRemote; /* O, =1 of handle/drive is remote, =0 if local */
ULONG flags; /* O, 0 */
ULONG isShare; /* O, 1 */
} REMOTEPACK; /* RP */
typedef REMOTEPACK *PREMOTEPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
struct StatDataPack {
typedef struct _STATDATAPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
ULONG fileType; /* O, bit0=1 data file */
ULONG flags; /* O, bit0=1 dirty, bit1=1 full-lock, bit2=1 shared */
ULONG progress; /* O, 0,1-99% pack progress */
PVOID morePtr; /* O, 0 */
ULONG fields; /* O, fields per record */
ULONG asMode; /* O, access-sharing-cache mode */
PSZ filenamePtr; /* O, filename used in open */
ULONG fileID; /* O, first byte of DBF file */
ULONG lastUpdate; /* O, high word=year,low byte=day, high byte=month */
ULONG records; /* O, data records (including "deleted") */
ULONG recordLength; /* O, record length */
ULONG xactionFlag; /* O, 0 */
ULONG encryptFlag; /* O, 0 */
PVOID herePtr; /* O, this file's control address */
ULONG memoHandle; /* O, handle of open memo file (0 if none) */
ULONG memoBlockSize; /* O, memo file block size */
ULONG memoFlags; /* O, bit0=1 dirty */
ULONG memoLastRecord; /* O, last accessed memo record (0 if none) */
ULONG memoLastSize; /* O, size of last accessed memo record (in bytes, +8) */
ULONG lockCount; /* O, number of full-locks in force */
} STATDATAPACK; /* SDP */
typedef STATDATAPACK *PSTATDATAPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _STATHANDLEPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
LONG ID; /* O, bit0=1 data file, bit0=1 index file */
} STATHANDLEPACK; /* SHP */
typedef STATHANDLEPACK *PSTATHANDLEPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _STATINDEXPACK {
ULONG func;
ULONG stat;
ULONG handle; /* I, handle to check */
ULONG fileType; /* O, bit0=0 index file */
ULONG flags; /* O, bit0=1 dirty, bit1=1 full-lock, bit2=1 shared */
ULONG progress; /* O, 0,1-99% reindex progress */
PVOID morePtr; /* O, 0 */
ULONG xbLink; /* O, XB file link handle */
ULONG asMode; /* O, access-sharing-cache mode */
PSZ filenamePtr; /* O, pointer to filename used in open */
ULONG fileID; /* O, "31ch" */
PSZ keyExpPtr; /* O, pointer to key expression */
ULONG keys; /* O, keys in file */
ULONG keyLength; /* O, key length */
ULONG keyRecNo; /* O, record number of current key */
PVOID keyPtr; /* O, ptr to current key value (valid to keyLength) */
PVOID herePtr; /* O, this file's control address */
ULONG codePage; /* O, code page at create time */
ULONG countryCode; /* O, country code at create time */
PVOID CTptr; /* O, collate table ptr, NULL=no collate table present */
ULONG nodeSize; /* O, node size */
ULONG sortFunction; /* O, sort function ID */
ULONG lockCount; /* O, number of full-locks in force */
} STATINDEXPACK; /* SIP */
typedef STATINDEXPACK *PSTATINDEXPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
typedef struct _XERRORPACK {
ULONG func;
ULONG stat; /* I, error to check */
ULONG errClass; /* O, class of error */
ULONG action; /* O, action recommended for error */
ULONG location; /* O, location of error */
} XERRORPACK; /* XEP */
typedef XERRORPACK *PXERRORPACK;
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
Access-Mode (required)
READONLY 0x00000000 read-only access open
WRITEONLY 0x00000001 write-only access open
READWRITE 0x00000002 read/write access open
Share-Mode (required)
DENYREADWRITE 0x00000010 no other process may share file
DENYWRITE 0x00000020 no other process may share file for write
DENYREAD 0x00000030 no other process may share file for read
DENYNONE 0x00000040 any process may share file
Inherit
NOINHERIT 0x00000080 child process does not inherit file handles
Cache
NO_LOCALITY 0x00000000 locality is not known
SEQ_LOCALITY 0x00010000 access will be mainly sequential
RND_LOCALITY 0x00020000 access will be mainly random
MIX_LOCALITY 0x00030000 access will be random with some sequential
SKIP_CACHE 0x00100000 I/O is not to go through the cache
WRITE_THROUGH 0x00400000 control returns only after disk is written to
Access- and Share-Mode values not explicitly listed are not valid. The file
access mode is a combination of ACCESS + SHARE + INHERIT + CACHE. Typical data
and index asMode is 0x00000042, though locality may be set accordingly (e.g.,
0x00020042 for mostly random access to the file). All values are mapped to the
appropriate flags for the OS being used.
The Cache mode options are valid for OPEN_DATA_XB and OPEN_INDEX_XB only; for
OPEN_FILE_DOS, the Cache values must be right-shifted by 8. The 'Skip Cache'
and 'Write Through' options are not inherited.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
The enumerator is a big-endian 16-bit value that serves to differentiate up to
65536 "identical", non- unique keys. It is attached to all keys of
DUPS_ALLOWED flagged index files (set at CREATE_INDEX_XB), and occupies the
last two bytes of the key. The first key of the type uses \0\0, the second
uses \0\1, and so on. This ordering of bytes is the reverse of x86 Intel
words, which uses little-endian format.
ΓòÉΓòÉΓòÉ <hidden> ΓòÉΓòÉΓòÉ
HIGH-VALUES signifies a sort value that is the highest possible (sorts last).
HIGH-VALUES for a character key would be 0xFF for each byte, or the 256th byte
of the collate-sequence table if an NLS sort (which is 0xFF also). For 16-bit
signed integer values, 0x7FFF is the highest. And so on...